/*!
 * bpmn-js - bpmn-navigated-viewer v4.0.3
 *
 * Copyright (c) 2014-present, camunda Services GmbH
 *
 * Released under the bpmn.io license
 * http://bpmn.io/license
 *
 * Source Code: https://github.com/bpmn-io/bpmn-js
 *
 * Date: 2019-07-22
 */
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define(factory) :
            (global = global || self, global.BpmnJS = factory());
}(this, function () { 'use strict';

    function createCommonjsModule(fn, module) {
        return module = { exports: {} }, fn(module, module.exports), module.exports;
    }

    var inherits_browser = createCommonjsModule(function (module) {
        if (typeof Object.create === 'function') {
            // implementation from standard node.js 'util' module
            module.exports = function inherits(ctor, superCtor) {
                ctor.super_ = superCtor;
                ctor.prototype = Object.create(superCtor.prototype, {
                    constructor: {
                        value: ctor,
                        enumerable: false,
                        writable: true,
                        configurable: true
                    }
                });
            };
        } else {
            // old school shim for old browsers
            module.exports = function inherits(ctor, superCtor) {
                ctor.super_ = superCtor;
                var TempCtor = function () {};
                TempCtor.prototype = superCtor.prototype;
                ctor.prototype = new TempCtor();
                ctor.prototype.constructor = ctor;
            };
        }
    });

    /**
     * Flatten array, one level deep.
     *
     * @param {Array<?>} arr
     *
     * @return {Array<?>}
     */

    var nativeToString = Object.prototype.toString;
    var nativeHasOwnProperty = Object.prototype.hasOwnProperty;
    function isUndefined(obj) {
        return obj === undefined;
    }
    function isDefined(obj) {
        return obj !== undefined;
    }
    function isArray(obj) {
        return nativeToString.call(obj) === '[object Array]';
    }
    function isObject(obj) {
        return nativeToString.call(obj) === '[object Object]';
    }
    function isNumber(obj) {
        return nativeToString.call(obj) === '[object Number]';
    }
    function isFunction(obj) {
        var tag = nativeToString.call(obj);
        return tag === '[object Function]' || tag === '[object AsyncFunction]' || tag === '[object GeneratorFunction]' || tag === '[object AsyncGeneratorFunction]' || tag === '[object Proxy]';
    }
    function isString(obj) {
        return nativeToString.call(obj) === '[object String]';
    }
    /**
     * Return true, if target owns a property with the given key.
     *
     * @param {Object} target
     * @param {String} key
     *
     * @return {Boolean}
     */

    function has(target, key) {
        return nativeHasOwnProperty.call(target, key);
    }

    /**
     * Find element in collection.
     *
     * @param  {Array|Object} collection
     * @param  {Function|Object} matcher
     *
     * @return {Object}
     */

    function find(collection, matcher) {
        matcher = toMatcher(matcher);
        var match;
        forEach(collection, function (val, key) {
            if (matcher(val, key)) {
                match = val;
                return false;
            }
        });
        return match;
    }
    /**
     * Find element in collection.
     *
     * @param  {Array|Object} collection
     * @param  {Function} matcher
     *
     * @return {Array} result
     */

    function filter(collection, matcher) {
        var result = [];
        forEach(collection, function (val, key) {
            if (matcher(val, key)) {
                result.push(val);
            }
        });
        return result;
    }
    /**
     * Iterate over collection; returning something
     * (non-undefined) will stop iteration.
     *
     * @param  {Array|Object} collection
     * @param  {Function} iterator
     *
     * @return {Object} return result that stopped the iteration
     */

    function forEach(collection, iterator) {
        var val, result;

        if (isUndefined(collection)) {
            return;
        }

        var convertKey = isArray(collection) ? toNum : identity;

        for (var key in collection) {
            if (has(collection, key)) {
                val = collection[key];
                result = iterator(val, convertKey(key));

                if (result === false) {
                    return val;
                }
            }
        }
    }
    /**
     * Reduce collection, returning a single result.
     *
     * @param  {Object|Array} collection
     * @param  {Function} iterator
     * @param  {Any} result
     *
     * @return {Any} result returned from last iterator
     */

    function reduce(collection, iterator, result) {
        forEach(collection, function (value, idx) {
            result = iterator(result, value, idx);
        });
        return result;
    }
    /**
     * Return true if every element in the collection
     * matches the criteria.
     *
     * @param  {Object|Array} collection
     * @param  {Function} matcher
     *
     * @return {Boolean}
     */

    function every(collection, matcher) {
        return reduce(collection, function (matches, val, key) {
            return matches && matcher(val, key);
        }, true);
    }
    /**
     * Return true if some elements in the collection
     * match the criteria.
     *
     * @param  {Object|Array} collection
     * @param  {Function} matcher
     *
     * @return {Boolean}
     */

    function some(collection, matcher) {
        return !!find(collection, matcher);
    }
    /**
     * Transform a collection into another collection
     * by piping each member through the given fn.
     *
     * @param  {Object|Array}   collection
     * @param  {Function} fn
     *
     * @return {Array} transformed collection
     */

    function map(collection, fn) {
        var result = [];
        forEach(collection, function (val, key) {
            result.push(fn(val, key));
        });
        return result;
    }
    /**
     * Create an object pattern matcher.
     *
     * @example
     *
     * const matcher = matchPattern({ id: 1 });
     *
     * var element = find(elements, matcher);
     *
     * @param  {Object} pattern
     *
     * @return {Function} matcherFn
     */

    function matchPattern(pattern) {
        return function (el) {
            return every(pattern, function (val, key) {
                return el[key] === val;
            });
        };
    }

    function toMatcher(matcher) {
        return isFunction(matcher) ? matcher : function (e) {
            return e === matcher;
        };
    }

    function identity(arg) {
        return arg;
    }

    function toNum(arg) {
        return Number(arg);
    }

    /**
     * Debounce fn, calling it only once if
     * the given time elapsed between calls.
     *
     * @param  {Function} fn
     * @param  {Number} timeout
     *
     * @return {Function} debounced function
     */
    function debounce(fn, timeout) {
        var timer;
        var lastArgs;
        var lastThis;
        var lastNow;

        function fire() {
            var now = Date.now();
            var scheduledDiff = lastNow + timeout - now;

            if (scheduledDiff > 0) {
                return schedule(scheduledDiff);
            }

            fn.apply(lastThis, lastArgs);
            timer = lastNow = lastArgs = lastThis = undefined;
        }

        function schedule(timeout) {
            timer = setTimeout(fire, timeout);
        }

        return function () {
            lastNow = Date.now();

            for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
                args[_key] = arguments[_key];
            }

            lastArgs = args;
            lastThis = this; // ensure an execution is scheduled

            if (!timer) {
                schedule(timeout);
            }
        };
    }
    /**
     * Bind function against target <this>.
     *
     * @param  {Function} fn
     * @param  {Object}   target
     *
     * @return {Function} bound function
     */

    function bind(fn, target) {
        return fn.bind(target);
    }

    function _extends() {
        _extends = Object.assign || function (target) {
            for (var i = 1; i < arguments.length; i++) {
                var source = arguments[i];

                for (var key in source) {
                    if (Object.prototype.hasOwnProperty.call(source, key)) {
                        target[key] = source[key];
                    }
                }
            }

            return target;
        };

        return _extends.apply(this, arguments);
    }

    /**
     * Convenience wrapper for `Object.assign`.
     *
     * @param {Object} target
     * @param {...Object} others
     *
     * @return {Object} the target
     */

    function assign(target) {
        for (var _len = arguments.length, others = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
            others[_key - 1] = arguments[_key];
        }

        return _extends.apply(void 0, [target].concat(others));
    }
    /**
     * Pick given properties from the target object.
     *
     * @param {Object} target
     * @param {Array} properties
     *
     * @return {Object} target
     */

    function pick(target, properties) {
        var result = {};
        var obj = Object(target);
        forEach(properties, function (prop) {
            if (prop in obj) {
                result[prop] = target[prop];
            }
        });
        return result;
    }
    /**
     * Pick all target properties, excluding the given ones.
     *
     * @param {Object} target
     * @param {Array} properties
     *
     * @return {Object} target
     */

    function omit(target, properties) {
        var result = {};
        var obj = Object(target);
        forEach(obj, function (prop, key) {
            if (properties.indexOf(key) === -1) {
                result[key] = prop;
            }
        });
        return result;
    }

    /**
     * Set attribute `name` to `val`, or get attr `name`.
     *
     * @param {Element} el
     * @param {String} name
     * @param {String} [val]
     * @api public
     */
    function attr(el, name, val) {
        // get
        if (arguments.length == 2) {
            return el.getAttribute(name);
        }

        // remove
        if (val === null) {
            return el.removeAttribute(name);
        }

        // set
        el.setAttribute(name, val);

        return el;
    }

    var indexOf = [].indexOf;

    var indexof = function(arr, obj){
        if (indexOf) return arr.indexOf(obj);
        for (var i = 0; i < arr.length; ++i) {
            if (arr[i] === obj) return i;
        }
        return -1;
    };

    /**
     * Taken from https://github.com/component/classes
     *
     * Without the component bits.
     */

    /**
     * Whitespace regexp.
     */

    var re = /\s+/;

    /**
     * toString reference.
     */

    var toString = Object.prototype.toString;

    /**
     * Wrap `el` in a `ClassList`.
     *
     * @param {Element} el
     * @return {ClassList}
     * @api public
     */

    function classes(el) {
        return new ClassList(el);
    }

    /**
     * Initialize a new ClassList for `el`.
     *
     * @param {Element} el
     * @api private
     */

    function ClassList(el) {
        if (!el || !el.nodeType) {
            throw new Error('A DOM element reference is required');
        }
        this.el = el;
        this.list = el.classList;
    }

    /**
     * Add class `name` if not already present.
     *
     * @param {String} name
     * @return {ClassList}
     * @api public
     */

    ClassList.prototype.add = function (name) {
        // classList
        if (this.list) {
            this.list.add(name);
            return this;
        }

        // fallback
        var arr = this.array();
        var i = indexof(arr, name);
        if (!~i) arr.push(name);
        this.el.className = arr.join(' ');
        return this;
    };

    /**
     * Remove class `name` when present, or
     * pass a regular expression to remove
     * any which match.
     *
     * @param {String|RegExp} name
     * @return {ClassList}
     * @api public
     */

    ClassList.prototype.remove = function (name) {
        if ('[object RegExp]' == toString.call(name)) {
            return this.removeMatching(name);
        }

        // classList
        if (this.list) {
            this.list.remove(name);
            return this;
        }

        // fallback
        var arr = this.array();
        var i = indexof(arr, name);
        if (~i) arr.splice(i, 1);
        this.el.className = arr.join(' ');
        return this;
    };

    /**
     * Remove all classes matching `re`.
     *
     * @param {RegExp} re
     * @return {ClassList}
     * @api private
     */

    ClassList.prototype.removeMatching = function (re) {
        var arr = this.array();
        for (var i = 0; i < arr.length; i++) {
            if (re.test(arr[i])) {
                this.remove(arr[i]);
            }
        }
        return this;
    };

    /**
     * Toggle class `name`, can force state via `force`.
     *
     * For browsers that support classList, but do not support `force` yet,
     * the mistake will be detected and corrected.
     *
     * @param {String} name
     * @param {Boolean} force
     * @return {ClassList}
     * @api public
     */

    ClassList.prototype.toggle = function (name, force) {
        // classList
        if (this.list) {
            if ('undefined' !== typeof force) {
                if (force !== this.list.toggle(name, force)) {
                    this.list.toggle(name); // toggle again to correct
                }
            } else {
                this.list.toggle(name);
            }
            return this;
        }

        // fallback
        if ('undefined' !== typeof force) {
            if (!force) {
                this.remove(name);
            } else {
                this.add(name);
            }
        } else {
            if (this.has(name)) {
                this.remove(name);
            } else {
                this.add(name);
            }
        }

        return this;
    };

    /**
     * Return an array of classes.
     *
     * @return {Array}
     * @api public
     */

    ClassList.prototype.array = function () {
        var className = this.el.getAttribute('class') || '';
        var str = className.replace(/^\s+|\s+$/g, '');
        var arr = str.split(re);
        if ('' === arr[0]) arr.shift();
        return arr;
    };

    /**
     * Check if class `name` is present.
     *
     * @param {String} name
     * @return {ClassList}
     * @api public
     */

    ClassList.prototype.has = ClassList.prototype.contains = function (name) {
        return this.list ? this.list.contains(name) : !!~indexof(this.array(), name);
    };

    /**
     * Remove all children from the given element.
     */
    function clear(el) {

        var c;

        while (el.childNodes.length) {
            c = el.childNodes[0];
            el.removeChild(c);
        }

        return el;
    }

    /**
     * Element prototype.
     */

    var proto = Element.prototype;

    /**
     * Vendor function.
     */

    var vendor = proto.matchesSelector
        || proto.webkitMatchesSelector
        || proto.mozMatchesSelector
        || proto.msMatchesSelector
        || proto.oMatchesSelector;

    /**
     * Expose `match()`.
     */

    var matchesSelector = match;

    /**
     * Match `el` to `selector`.
     *
     * @param {Element} el
     * @param {String} selector
     * @return {Boolean}
     * @api public
     */

    function match(el, selector) {
        if (vendor) return vendor.call(el, selector);
        var nodes = el.parentNode.querySelectorAll(selector);
        for (var i = 0; i < nodes.length; ++i) {
            if (nodes[i] == el) return true;
        }
        return false;
    }

    var closest = function (element, selector, checkYoSelf) {
        var parent = checkYoSelf ? element : element.parentNode;

        while (parent && parent !== document) {
            if (matchesSelector(parent, selector)) return parent;
            parent = parent.parentNode;
        }
    };

    var bind$1 = window.addEventListener ? 'addEventListener' : 'attachEvent',
        unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent',
        prefix = bind$1 !== 'addEventListener' ? 'on' : '';

    /**
     * Bind `el` event `type` to `fn`.
     *
     * @param {Element} el
     * @param {String} type
     * @param {Function} fn
     * @param {Boolean} capture
     * @return {Function}
     * @api public
     */

    var bind_1 = function(el, type, fn, capture){
        el[bind$1](prefix + type, fn, capture || false);
        return fn;
    };

    /**
     * Unbind `el` event `type`'s callback `fn`.
     *
     * @param {Element} el
     * @param {String} type
     * @param {Function} fn
     * @param {Boolean} capture
     * @return {Function}
     * @api public
     */

    var unbind_1 = function(el, type, fn, capture){
        el[unbind](prefix + type, fn, capture || false);
        return fn;
    };

    var componentEvent = {
        bind: bind_1,
        unbind: unbind_1
    };

    /**
     * Module dependencies.
     */



    /**
     * Delegate event `type` to `selector`
     * and invoke `fn(e)`. A callback function
     * is returned which may be passed to `.unbind()`.
     *
     * @param {Element} el
     * @param {String} selector
     * @param {String} type
     * @param {Function} fn
     * @param {Boolean} capture
     * @return {Function}
     * @api public
     */

        // Some events don't bubble, so we want to bind to the capture phase instead
        // when delegating.
    var forceCaptureEvents = ['focus', 'blur'];

    var bind$1$1 = function(el, selector, type, fn, capture){
        if (forceCaptureEvents.indexOf(type) !== -1) capture = true;

        return componentEvent.bind(el, type, function(e){
            var target = e.target || e.srcElement;
            e.delegateTarget = closest(target, selector, true);
            if (e.delegateTarget) fn.call(el, e);
        }, capture);
    };

    /**
     * Unbind event `type`'s callback `fn`.
     *
     * @param {Element} el
     * @param {String} type
     * @param {Function} fn
     * @param {Boolean} capture
     * @api public
     */

    var unbind$1 = function(el, type, fn, capture){
        if (forceCaptureEvents.indexOf(type) !== -1) capture = true;

        componentEvent.unbind(el, type, fn, capture);
    };

    var delegateEvents = {
        bind: bind$1$1,
        unbind: unbind$1
    };

    /**
     * Expose `parse`.
     */

    var domify = parse;

    /**
     * Tests for browser support.
     */

    var innerHTMLBug = false;
    var bugTestDiv;
    if (typeof document !== 'undefined') {
        bugTestDiv = document.createElement('div');
        // Setup
        bugTestDiv.innerHTML = '  <link/><table></table><a href="/a">a</a><input type="checkbox"/>';
        // Make sure that link elements get serialized correctly by innerHTML
        // This requires a wrapper element in IE
        innerHTMLBug = !bugTestDiv.getElementsByTagName('link').length;
        bugTestDiv = undefined;
    }

    /**
     * Wrap map from jquery.
     */

    var map$1 = {
        legend: [1, '<fieldset>', '</fieldset>'],
        tr: [2, '<table><tbody>', '</tbody></table>'],
        col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
        // for script/link/style tags to work in IE6-8, you have to wrap
        // in a div with a non-whitespace character in front, ha!
        _default: innerHTMLBug ? [1, 'X<div>', '</div>'] : [0, '', '']
    };

    map$1.td =
        map$1.th = [3, '<table><tbody><tr>', '</tr></tbody></table>'];

    map$1.option =
        map$1.optgroup = [1, '<select multiple="multiple">', '</select>'];

    map$1.thead =
        map$1.tbody =
            map$1.colgroup =
                map$1.caption =
                    map$1.tfoot = [1, '<table>', '</table>'];

    map$1.polyline =
        map$1.ellipse =
            map$1.polygon =
                map$1.circle =
                    map$1.text =
                        map$1.line =
                            map$1.path =
                                map$1.rect =
                                    map$1.g = [1, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>'];

    /**
     * Parse `html` and return a DOM Node instance, which could be a TextNode,
     * HTML DOM Node of some kind (<div> for example), or a DocumentFragment
     * instance, depending on the contents of the `html` string.
     *
     * @param {String} html - HTML string to "domify"
     * @param {Document} doc - The `document` instance to create the Node for
     * @return {DOMNode} the TextNode, DOM Node, or DocumentFragment instance
     * @api private
     */

    function parse(html, doc) {
        if ('string' != typeof html) throw new TypeError('String expected');

        // default to the global `document` object
        if (!doc) doc = document;

        // tag name
        var m = /<([\w:]+)/.exec(html);
        if (!m) return doc.createTextNode(html);

        html = html.replace(/^\s+|\s+$/g, ''); // Remove leading/trailing whitespace

        var tag = m[1];

        // body support
        if (tag == 'body') {
            var el = doc.createElement('html');
            el.innerHTML = html;
            return el.removeChild(el.lastChild);
        }

        // wrap map
        var wrap = map$1[tag] || map$1._default;
        var depth = wrap[0];
        var prefix = wrap[1];
        var suffix = wrap[2];
        var el = doc.createElement('div');
        el.innerHTML = prefix + html + suffix;
        while (depth--) el = el.lastChild;

        // one element
        if (el.firstChild == el.lastChild) {
            return el.removeChild(el.firstChild);
        }

        // several elements
        var fragment = doc.createDocumentFragment();
        while (el.firstChild) {
            fragment.appendChild(el.removeChild(el.firstChild));
        }

        return fragment;
    }

    var proto$1 = typeof Element !== 'undefined' ? Element.prototype : {};
    var vendor$1 = proto$1.matches
        || proto$1.matchesSelector
        || proto$1.webkitMatchesSelector
        || proto$1.mozMatchesSelector
        || proto$1.msMatchesSelector
        || proto$1.oMatchesSelector;

    var matchesSelector$1 = match$1;

    /**
     * Match `el` to `selector`.
     *
     * @param {Element} el
     * @param {String} selector
     * @return {Boolean}
     * @api public
     */

    function match$1(el, selector) {
        if (!el || el.nodeType !== 1) return false;
        if (vendor$1) return vendor$1.call(el, selector);
        var nodes = el.parentNode.querySelectorAll(selector);
        for (var i = 0; i < nodes.length; i++) {
            if (nodes[i] == el) return true;
        }
        return false;
    }

    function query(selector, el) {
        el = el || document;

        return el.querySelector(selector);
    }

    function remove(el) {
        el.parentNode && el.parentNode.removeChild(el);
    }

    function ensureImported(element, target) {

        if (element.ownerDocument !== target.ownerDocument) {
            try {
                // may fail on webkit
                return target.ownerDocument.importNode(element, true);
            } catch (e) {
                // ignore
            }
        }

        return element;
    }

    /**
     * appendTo utility
     */

    /**
     * Append a node to a target element and return the appended node.
     *
     * @param  {SVGElement} element
     * @param  {SVGElement} target
     *
     * @return {SVGElement} the appended node
     */
    function appendTo(element, target) {
        return target.appendChild(ensureImported(element, target));
    }

    /**
     * append utility
     */

    /**
     * Append a node to an element
     *
     * @param  {SVGElement} element
     * @param  {SVGElement} node
     *
     * @return {SVGElement} the element
     */
    function append(target, node) {
        appendTo(node, target);
        return target;
    }

    /**
     * attribute accessor utility
     */

    var LENGTH_ATTR = 2;

    var CSS_PROPERTIES = {
        'alignment-baseline': 1,
        'baseline-shift': 1,
        'clip': 1,
        'clip-path': 1,
        'clip-rule': 1,
        'color': 1,
        'color-interpolation': 1,
        'color-interpolation-filters': 1,
        'color-profile': 1,
        'color-rendering': 1,
        'cursor': 1,
        'direction': 1,
        'display': 1,
        'dominant-baseline': 1,
        'enable-background': 1,
        'fill': 1,
        'fill-opacity': 1,
        'fill-rule': 1,
        'filter': 1,
        'flood-color': 1,
        'flood-opacity': 1,
        'font': 1,
        'font-family': 1,
        'font-size': LENGTH_ATTR,
        'font-size-adjust': 1,
        'font-stretch': 1,
        'font-style': 1,
        'font-variant': 1,
        'font-weight': 1,
        'glyph-orientation-horizontal': 1,
        'glyph-orientation-vertical': 1,
        'image-rendering': 1,
        'kerning': 1,
        'letter-spacing': 1,
        'lighting-color': 1,
        'marker': 1,
        'marker-end': 1,
        'marker-mid': 1,
        'marker-start': 1,
        'mask': 1,
        'opacity': 1,
        'overflow': 1,
        'pointer-events': 1,
        'shape-rendering': 1,
        'stop-color': 1,
        'stop-opacity': 1,
        'stroke': 1,
        'stroke-dasharray': 1,
        'stroke-dashoffset': 1,
        'stroke-linecap': 1,
        'stroke-linejoin': 1,
        'stroke-miterlimit': 1,
        'stroke-opacity': 1,
        'stroke-width': LENGTH_ATTR,
        'text-anchor': 1,
        'text-decoration': 1,
        'text-rendering': 1,
        'unicode-bidi': 1,
        'visibility': 1,
        'word-spacing': 1,
        'writing-mode': 1
    };


    function getAttribute(node, name) {
        if (CSS_PROPERTIES[name]) {
            return node.style[name];
        } else {
            return node.getAttributeNS(null, name);
        }
    }

    function setAttribute(node, name, value) {
        var hyphenated = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();

        var type = CSS_PROPERTIES[hyphenated];

        if (type) {
            // append pixel unit, unless present
            if (type === LENGTH_ATTR && typeof value === 'number') {
                value = String(value) + 'px';
            }

            node.style[hyphenated] = value;
        } else {
            node.setAttributeNS(null, name, value);
        }
    }

    function setAttributes(node, attrs) {

        var names = Object.keys(attrs), i, name;

        for (i = 0, name; (name = names[i]); i++) {
            setAttribute(node, name, attrs[name]);
        }
    }

    /**
     * Gets or sets raw attributes on a node.
     *
     * @param  {SVGElement} node
     * @param  {Object} [attrs]
     * @param  {String} [name]
     * @param  {String} [value]
     *
     * @return {String}
     */
    function attr$1(node, name, value) {
        if (typeof name === 'string') {
            if (value !== undefined) {
                setAttribute(node, name, value);
            } else {
                return getAttribute(node, name);
            }
        } else {
            setAttributes(node, name);
        }

        return node;
    }

    /**
     * Clear utility
     */
    function index(arr, obj) {
        if (arr.indexOf) {
            return arr.indexOf(obj);
        }


        for (var i = 0; i < arr.length; ++i) {
            if (arr[i] === obj) {
                return i;
            }
        }

        return -1;
    }

    var re$1 = /\s+/;

    var toString$1 = Object.prototype.toString;

    function defined(o) {
        return typeof o !== 'undefined';
    }

    /**
     * Wrap `el` in a `ClassList`.
     *
     * @param {Element} el
     * @return {ClassList}
     * @api public
     */

    function classes$1(el) {
        return new ClassList$1(el);
    }

    function ClassList$1(el) {
        if (!el || !el.nodeType) {
            throw new Error('A DOM element reference is required');
        }
        this.el = el;
        this.list = el.classList;
    }

    /**
     * Add class `name` if not already present.
     *
     * @param {String} name
     * @return {ClassList}
     * @api public
     */

    ClassList$1.prototype.add = function(name) {

        // classList
        if (this.list) {
            this.list.add(name);
            return this;
        }

        // fallback
        var arr = this.array();
        var i = index(arr, name);
        if (!~i) {
            arr.push(name);
        }

        if (defined(this.el.className.baseVal)) {
            this.el.className.baseVal = arr.join(' ');
        } else {
            this.el.className = arr.join(' ');
        }

        return this;
    };

    /**
     * Remove class `name` when present, or
     * pass a regular expression to remove
     * any which match.
     *
     * @param {String|RegExp} name
     * @return {ClassList}
     * @api public
     */

    ClassList$1.prototype.remove = function(name) {
        if ('[object RegExp]' === toString$1.call(name)) {
            return this.removeMatching(name);
        }

        // classList
        if (this.list) {
            this.list.remove(name);
            return this;
        }

        // fallback
        var arr = this.array();
        var i = index(arr, name);
        if (~i) {
            arr.splice(i, 1);
        }
        this.el.className.baseVal = arr.join(' ');
        return this;
    };

    /**
     * Remove all classes matching `re`.
     *
     * @param {RegExp} re
     * @return {ClassList}
     * @api private
     */

    ClassList$1.prototype.removeMatching = function(re) {
        var arr = this.array();
        for (var i = 0; i < arr.length; i++) {
            if (re.test(arr[i])) {
                this.remove(arr[i]);
            }
        }
        return this;
    };

    /**
     * Toggle class `name`, can force state via `force`.
     *
     * For browsers that support classList, but do not support `force` yet,
     * the mistake will be detected and corrected.
     *
     * @param {String} name
     * @param {Boolean} force
     * @return {ClassList}
     * @api public
     */

    ClassList$1.prototype.toggle = function(name, force) {
        // classList
        if (this.list) {
            if (defined(force)) {
                if (force !== this.list.toggle(name, force)) {
                    this.list.toggle(name); // toggle again to correct
                }
            } else {
                this.list.toggle(name);
            }
            return this;
        }

        // fallback
        if (defined(force)) {
            if (!force) {
                this.remove(name);
            } else {
                this.add(name);
            }
        } else {
            if (this.has(name)) {
                this.remove(name);
            } else {
                this.add(name);
            }
        }

        return this;
    };

    /**
     * Return an array of classes.
     *
     * @return {Array}
     * @api public
     */

    ClassList$1.prototype.array = function() {
        var className = this.el.getAttribute('class') || '';
        var str = className.replace(/^\s+|\s+$/g, '');
        var arr = str.split(re$1);
        if ('' === arr[0]) {
            arr.shift();
        }
        return arr;
    };

    /**
     * Check if class `name` is present.
     *
     * @param {String} name
     * @return {ClassList}
     * @api public
     */

    ClassList$1.prototype.has =
        ClassList$1.prototype.contains = function(name) {
            return (
                this.list ?
                    this.list.contains(name) :
                    !! ~index(this.array(), name)
            );
        };

    function remove$1(element) {
        var parent = element.parentNode;

        if (parent) {
            parent.removeChild(element);
        }

        return element;
    }

    /**
     * Clear utility
     */

    /**
     * Removes all children from the given element
     *
     * @param  {DOMElement} element
     * @return {DOMElement} the element (for chaining)
     */
    function clear$1(element) {
        var child;

        while ((child = element.firstChild)) {
            remove$1(child);
        }

        return element;
    }

    var ns = {
        svg: 'http://www.w3.org/2000/svg'
    };

    /**
     * DOM parsing utility
     */

    var SVG_START = '<svg xmlns="' + ns.svg + '"';

    function parse$1(svg) {

        var unwrap = false;

        // ensure we import a valid svg document
        if (svg.substring(0, 4) === '<svg') {
            if (svg.indexOf(ns.svg) === -1) {
                svg = SVG_START + svg.substring(4);
            }
        } else {
            // namespace svg
            svg = SVG_START + '>' + svg + '</svg>';
            unwrap = true;
        }

        var parsed = parseDocument(svg);

        if (!unwrap) {
            return parsed;
        }

        var fragment = document.createDocumentFragment();

        var parent = parsed.firstChild;

        while (parent.firstChild) {
            fragment.appendChild(parent.firstChild);
        }

        return fragment;
    }

    function parseDocument(svg) {

        var parser;

        // parse
        parser = new DOMParser();
        parser.async = false;

        return parser.parseFromString(svg, 'text/xml');
    }

    /**
     * Create utility for SVG elements
     */


    /**
     * Create a specific type from name or SVG markup.
     *
     * @param {String} name the name or markup of the element
     * @param {Object} [attrs] attributes to set on the element
     *
     * @returns {SVGElement}
     */
    function create(name, attrs) {
        var element;

        if (name.charAt(0) === '<') {
            element = parse$1(name).firstChild;
            element = document.importNode(element, true);
        } else {
            element = document.createElementNS(ns.svg, name);
        }

        if (attrs) {
            attr$1(element, attrs);
        }

        return element;
    }

    /**
     * Geometry helpers
     */

        // fake node used to instantiate svg geometry elements
    var node = create('svg');

    function extend(object, props) {
        var i, k, keys = Object.keys(props);

        for (i = 0; (k = keys[i]); i++) {
            object[k] = props[k];
        }

        return object;
    }

    /**
     * Create matrix via args.
     *
     * @example
     *
     * createMatrix({ a: 1, b: 1 });
     * createMatrix();
     * createMatrix(1, 2, 0, 0, 30, 20);
     *
     * @return {SVGMatrix}
     */
    function createMatrix(a, b, c, d, e, f) {
        var matrix = node.createSVGMatrix();

        switch (arguments.length) {
            case 0:
                return matrix;
            case 1:
                return extend(matrix, a);
            case 6:
                return extend(matrix, {
                    a: a,
                    b: b,
                    c: c,
                    d: d,
                    e: e,
                    f: f
                });
        }
    }

    function createTransform(matrix) {
        if (matrix) {
            return node.createSVGTransformFromMatrix(matrix);
        } else {
            return node.createSVGTransform();
        }
    }

    /**
     * Serialization util
     */

    var TEXT_ENTITIES = /([&<>]{1})/g;
    var ATTR_ENTITIES = /([\n\r"]{1})/g;

    var ENTITY_REPLACEMENT = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '\''
    };

    function escape(str, pattern) {

        function replaceFn(match, entity) {
            return ENTITY_REPLACEMENT[entity] || entity;
        }

        return str.replace(pattern, replaceFn);
    }

    function serialize(node, output) {

        var i, len, attrMap, attrNode, childNodes;

        switch (node.nodeType) {
            // TEXT
            case 3:
                // replace special XML characters
                output.push(escape(node.textContent, TEXT_ENTITIES));
                break;

            // ELEMENT
            case 1:
                output.push('<', node.tagName);

                if (node.hasAttributes()) {
                    attrMap = node.attributes;
                    for (i = 0, len = attrMap.length; i < len; ++i) {
                        attrNode = attrMap.item(i);
                        output.push(' ', attrNode.name, '="', escape(attrNode.value, ATTR_ENTITIES), '"');
                    }
                }

                if (node.hasChildNodes()) {
                    output.push('>');
                    childNodes = node.childNodes;
                    for (i = 0, len = childNodes.length; i < len; ++i) {
                        serialize(childNodes.item(i), output);
                    }
                    output.push('</', node.tagName, '>');
                } else {
                    output.push('/>');
                }
                break;

            // COMMENT
            case 8:
                output.push('<!--', escape(node.nodeValue, TEXT_ENTITIES), '-->');
                break;

            // CDATA
            case 4:
                output.push('<![CDATA[', node.nodeValue, ']]>');
                break;

            default:
                throw new Error('unable to handle node ' + node.nodeType);
        }

        return output;
    }

    /**
     * innerHTML like functionality for SVG elements.
     * based on innerSVG (https://code.google.com/p/innersvg)
     */


    function set(element, svg) {

        var parsed = parse$1(svg);

        // clear element contents
        clear$1(element);

        if (!svg) {
            return;
        }

        if (!isFragment(parsed)) {
            // extract <svg> from parsed document
            parsed = parsed.documentElement;
        }

        var nodes = slice(parsed.childNodes);

        // import + append each node
        for (var i = 0; i < nodes.length; i++) {
            appendTo(nodes[i], element);
        }

    }

    function get(element) {
        var child = element.firstChild,
            output = [];

        while (child) {
            serialize(child, output);
            child = child.nextSibling;
        }

        return output.join('');
    }

    function isFragment(node) {
        return node.nodeName === '#document-fragment';
    }

    function innerSVG(element, svg) {

        if (svg !== undefined) {

            try {
                set(element, svg);
            } catch (e) {
                throw new Error('error parsing SVG: ' + e.message);
            }

            return element;
        } else {
            return get(element);
        }
    }


    function slice(arr) {
        return Array.prototype.slice.call(arr);
    }

    /**
     * transform accessor utility
     */

    function wrapMatrix(transformList, transform) {
        if (transform instanceof SVGMatrix) {
            return transformList.createSVGTransformFromMatrix(transform);
        }

        return transform;
    }


    function setTransforms(transformList, transforms) {
        var i, t;

        transformList.clear();

        for (i = 0; (t = transforms[i]); i++) {
            transformList.appendItem(wrapMatrix(transformList, t));
        }
    }

    /**
     * Get or set the transforms on the given node.
     *
     * @param {SVGElement} node
     * @param  {SVGTransform|SVGMatrix|Array<SVGTransform|SVGMatrix>} [transforms]
     *
     * @return {SVGTransform} the consolidated transform
     */
    function transform(node, transforms) {
        var transformList = node.transform.baseVal;

        if (transforms) {

            if (!Array.isArray(transforms)) {
                transforms = [ transforms ];
            }

            setTransforms(transformList, transforms);
        }

        return transformList.consolidate();
    }

    var CLASS_PATTERN = /^class /;

    function isClass(fn) {
        return CLASS_PATTERN.test(fn.toString());
    }

    function isArray$1(obj) {
        return Object.prototype.toString.call(obj) === '[object Array]';
    }

    function annotate() {
        var args = Array.prototype.slice.call(arguments);

        if (args.length === 1 && isArray$1(args[0])) {
            args = args[0];
        }

        var fn = args.pop();

        fn.$inject = args;

        return fn;
    }

    // Current limitations:
    // - can't put into "function arg" comments
    // function /* (no parenthesis like this) */ (){}
    // function abc( /* xx (no parenthesis like this) */ a, b) {}
    //
    // Just put the comment before function or inside:
    // /* (((this is fine))) */ function(a, b) {}
    // function abc(a) { /* (((this is fine))) */}
    //
    // - can't reliably auto-annotate constructor; we'll match the
    // first constructor(...) pattern found which may be the one
    // of a nested class, too.

    var CONSTRUCTOR_ARGS = /constructor\s*[^(]*\(\s*([^)]*)\)/m;
    var FN_ARGS = /^function\s*[^(]*\(\s*([^)]*)\)/m;
    var FN_ARG = /\/\*([^*]*)\*\//m;

    function parse$2(fn) {

        if (typeof fn !== 'function') {
            throw new Error('Cannot annotate "' + fn + '". Expected a function!');
        }

        var match = fn.toString().match(isClass(fn) ? CONSTRUCTOR_ARGS : FN_ARGS);

        // may parse class without constructor
        if (!match) {
            return [];
        }

        return match[1] && match[1].split(',').map(function (arg) {
            match = arg.match(FN_ARG);
            return match ? match[1].trim() : arg.trim();
        }) || [];
    }

    function Module() {
        var providers = [];

        this.factory = function (name, factory) {
            providers.push([name, 'factory', factory]);
            return this;
        };

        this.value = function (name, value) {
            providers.push([name, 'value', value]);
            return this;
        };

        this.type = function (name, type) {
            providers.push([name, 'type', type]);
            return this;
        };

        this.forEach = function (iterator) {
            providers.forEach(iterator);
        };
    }

    var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

    function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

    function Injector(modules, parent) {
        parent = parent || {
            get: function get(name, strict) {
                currentlyResolving.push(name);

                if (strict === false) {
                    return null;
                } else {
                    throw error('No provider for "' + name + '"!');
                }
            }
        };

        var currentlyResolving = [];
        var providers = this._providers = Object.create(parent._providers || null);
        var instances = this._instances = Object.create(null);

        var self = instances.injector = this;

        var error = function error(msg) {
            var stack = currentlyResolving.join(' -> ');
            currentlyResolving.length = 0;
            return new Error(stack ? msg + ' (Resolving: ' + stack + ')' : msg);
        };

        /**
         * Return a named service.
         *
         * @param {String} name
         * @param {Boolean} [strict=true] if false, resolve missing services to null
         *
         * @return {Object}
         */
        var get = function get(name, strict) {
            if (!providers[name] && name.indexOf('.') !== -1) {
                var parts = name.split('.');
                var pivot = get(parts.shift());

                while (parts.length) {
                    pivot = pivot[parts.shift()];
                }

                return pivot;
            }

            if (hasProp(instances, name)) {
                return instances[name];
            }

            if (hasProp(providers, name)) {
                if (currentlyResolving.indexOf(name) !== -1) {
                    currentlyResolving.push(name);
                    throw error('Cannot resolve circular dependency!');
                }

                currentlyResolving.push(name);
                instances[name] = providers[name][0](providers[name][1]);
                currentlyResolving.pop();

                return instances[name];
            }

            return parent.get(name, strict);
        };

        var fnDef = function fnDef(fn) {
            var locals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

            if (typeof fn !== 'function') {
                if (isArray$1(fn)) {
                    fn = annotate(fn.slice());
                } else {
                    throw new Error('Cannot invoke "' + fn + '". Expected a function!');
                }
            }

            var inject = fn.$inject || parse$2(fn);
            var dependencies = inject.map(function (dep) {
                if (hasProp(locals, dep)) {
                    return locals[dep];
                } else {
                    return get(dep);
                }
            });

            return {
                fn: fn,
                dependencies: dependencies
            };
        };

        var instantiate = function instantiate(Type) {
            var _fnDef = fnDef(Type),
                dependencies = _fnDef.dependencies,
                fn = _fnDef.fn;

            return new (Function.prototype.bind.apply(fn, [null].concat(_toConsumableArray(dependencies))))();
        };

        var invoke = function invoke(func, context, locals) {
            var _fnDef2 = fnDef(func, locals),
                dependencies = _fnDef2.dependencies,
                fn = _fnDef2.fn;

            return fn.call.apply(fn, [context].concat(_toConsumableArray(dependencies)));
        };

        var createPrivateInjectorFactory = function createPrivateInjectorFactory(privateChildInjector) {
            return annotate(function (key) {
                return privateChildInjector.get(key);
            });
        };

        var createChild = function createChild(modules, forceNewInstances) {
            if (forceNewInstances && forceNewInstances.length) {
                var fromParentModule = Object.create(null);
                var matchedScopes = Object.create(null);

                var privateInjectorsCache = [];
                var privateChildInjectors = [];
                var privateChildFactories = [];

                var provider;
                var cacheIdx;
                var privateChildInjector;
                var privateChildInjectorFactory;
                for (var name in providers) {
                    provider = providers[name];

                    if (forceNewInstances.indexOf(name) !== -1) {
                        if (provider[2] === 'private') {
                            cacheIdx = privateInjectorsCache.indexOf(provider[3]);
                            if (cacheIdx === -1) {
                                privateChildInjector = provider[3].createChild([], forceNewInstances);
                                privateChildInjectorFactory = createPrivateInjectorFactory(privateChildInjector);
                                privateInjectorsCache.push(provider[3]);
                                privateChildInjectors.push(privateChildInjector);
                                privateChildFactories.push(privateChildInjectorFactory);
                                fromParentModule[name] = [privateChildInjectorFactory, name, 'private', privateChildInjector];
                            } else {
                                fromParentModule[name] = [privateChildFactories[cacheIdx], name, 'private', privateChildInjectors[cacheIdx]];
                            }
                        } else {
                            fromParentModule[name] = [provider[2], provider[1]];
                        }
                        matchedScopes[name] = true;
                    }

                    if ((provider[2] === 'factory' || provider[2] === 'type') && provider[1].$scope) {
                        /* jshint -W083 */
                        forceNewInstances.forEach(function (scope) {
                            if (provider[1].$scope.indexOf(scope) !== -1) {
                                fromParentModule[name] = [provider[2], provider[1]];
                                matchedScopes[scope] = true;
                            }
                        });
                    }
                }

                forceNewInstances.forEach(function (scope) {
                    if (!matchedScopes[scope]) {
                        throw new Error('No provider for "' + scope + '". Cannot use provider from the parent!');
                    }
                });

                modules.unshift(fromParentModule);
            }

            return new Injector(modules, self);
        };

        var factoryMap = {
            factory: invoke,
            type: instantiate,
            value: function value(_value) {
                return _value;
            }
        };

        modules.forEach(function (module) {

            function arrayUnwrap(type, value) {
                if (type !== 'value' && isArray$1(value)) {
                    value = annotate(value.slice());
                }

                return value;
            }

            // TODO(vojta): handle wrong inputs (modules)
            if (module instanceof Module) {
                module.forEach(function (provider) {
                    var name = provider[0];
                    var type = provider[1];
                    var value = provider[2];

                    providers[name] = [factoryMap[type], arrayUnwrap(type, value), type];
                });
            } else if ((typeof module === 'undefined' ? 'undefined' : _typeof(module)) === 'object') {
                if (module.__exports__) {
                    var clonedModule = Object.keys(module).reduce(function (m, key) {
                        if (key.substring(0, 2) !== '__') {
                            m[key] = module[key];
                        }
                        return m;
                    }, Object.create(null));

                    var privateInjector = new Injector((module.__modules__ || []).concat([clonedModule]), self);
                    var getFromPrivateInjector = annotate(function (key) {
                        return privateInjector.get(key);
                    });
                    module.__exports__.forEach(function (key) {
                        providers[key] = [getFromPrivateInjector, key, 'private', privateInjector];
                    });
                } else {
                    Object.keys(module).forEach(function (name) {
                        if (module[name][2] === 'private') {
                            providers[name] = module[name];
                            return;
                        }

                        var type = module[name][0];
                        var value = module[name][1];

                        providers[name] = [factoryMap[type], arrayUnwrap(type, value), type];
                    });
                }
            }
        });

        // public API
        this.get = get;
        this.invoke = invoke;
        this.instantiate = instantiate;
        this.createChild = createChild;
    }

    // helpers /////////////////

    function hasProp(obj, prop) {
        return Object.hasOwnProperty.call(obj, prop);
    }

    var DEFAULT_RENDER_PRIORITY = 1000;

    /**
     * The base implementation of shape and connection renderers.
     *
     * @param {EventBus} eventBus
     * @param {Number} [renderPriority=1000]
     */
    function BaseRenderer(eventBus, renderPriority) {
        var self = this;

        renderPriority = renderPriority || DEFAULT_RENDER_PRIORITY;

        eventBus.on([ 'render.shape', 'render.connection' ], renderPriority, function(evt, context) {
            var type = evt.type,
                element = context.element,
                visuals = context.gfx;

            if (self.canRender(element)) {
                if (type === 'render.shape') {
                    return self.drawShape(visuals, element);
                } else {
                    return self.drawConnection(visuals, element);
                }
            }
        });

        eventBus.on([ 'render.getShapePath', 'render.getConnectionPath'], renderPriority, function(evt, element) {
            if (self.canRender(element)) {
                if (evt.type === 'render.getShapePath') {
                    return self.getShapePath(element);
                } else {
                    return self.getConnectionPath(element);
                }
            }
        });
    }

    /**
     * Should check whether *this* renderer can render
     * the element/connection.
     *
     * @param {element} element
     *
     * @returns {Boolean}
     */
    BaseRenderer.prototype.canRender = function() {};

    /**
     * Provides the shape's snap svg element to be drawn on the `canvas`.
     *
     * @param {djs.Graphics} visuals
     * @param {Shape} shape
     *
     * @returns {Snap.svg} [returns a Snap.svg paper element ]
     */
    BaseRenderer.prototype.drawShape = function() {};

    /**
     * Provides the shape's snap svg element to be drawn on the `canvas`.
     *
     * @param {djs.Graphics} visuals
     * @param {Connection} connection
     *
     * @returns {Snap.svg} [returns a Snap.svg paper element ]
     */
    BaseRenderer.prototype.drawConnection = function() {};

    /**
     * Gets the SVG path of a shape that represents it's visual bounds.
     *
     * @param {Shape} shape
     *
     * @return {string} svg path
     */
    BaseRenderer.prototype.getShapePath = function() {};

    /**
     * Gets the SVG path of a connection that represents it's visual bounds.
     *
     * @param {Connection} connection
     *
     * @return {string} svg path
     */
    BaseRenderer.prototype.getConnectionPath = function() {};

    function componentsToPath(elements) {
        return elements.join(',').replace(/,?([A-z]),?/g, '$1');
    }

    function toSVGPoints(points) {
        var result = '';

        for (var i = 0, p; (p = points[i]); i++) {
            result += p.x + ',' + p.y + ' ';
        }

        return result;
    }

    function createLine(points, attrs) {

        var line = create('polyline');
        attr$1(line, { points: toSVGPoints(points) });

        if (attrs) {
            attr$1(line, attrs);
        }

        return line;
    }

    function updateLine(gfx, points) {
        attr$1(gfx, { points: toSVGPoints(points) });

        return gfx;
    }

    /**
     * Returns the surrounding bbox for all elements in
     * the array or the element primitive.
     *
     * @param {Array<djs.model.Shape>|djs.model.Shape} elements
     * @param {Boolean} stopRecursion
     */
    function getBBox(elements, stopRecursion) {

        stopRecursion = !!stopRecursion;
        if (!isArray(elements)) {
            elements = [elements];
        }

        var minX,
            minY,
            maxX,
            maxY;

        forEach(elements, function(element) {

            // If element is a connection the bbox must be computed first
            var bbox = element;
            if (element.waypoints && !stopRecursion) {
                bbox = getBBox(element.waypoints, true);
            }

            var x = bbox.x,
                y = bbox.y,
                height = bbox.height || 0,
                width = bbox.width || 0;

            if (x < minX || minX === undefined) {
                minX = x;
            }
            if (y < minY || minY === undefined) {
                minY = y;
            }

            if ((x + width) > maxX || maxX === undefined) {
                maxX = x + width;
            }
            if ((y + height) > maxY || maxY === undefined) {
                maxY = y + height;
            }
        });

        return {
            x: minX,
            y: minY,
            height: maxY - minY,
            width: maxX - minX
        };
    }


    function getType(element) {

        if ('waypoints' in element) {
            return 'connection';
        }

        if ('x' in element) {
            return 'shape';
        }

        return 'root';
    }

    function isFrameElement(element) {

        return !!(element && element.isFrame);
    }

    // apply default renderer with lowest possible priority
    // so that it only kicks in if noone else could render
    var DEFAULT_RENDER_PRIORITY$1 = 1;

    /**
     * The default renderer used for shapes and connections.
     *
     * @param {EventBus} eventBus
     * @param {Styles} styles
     */
    function DefaultRenderer(eventBus, styles) {
        //
        BaseRenderer.call(this, eventBus, DEFAULT_RENDER_PRIORITY$1);

        this.CONNECTION_STYLE = styles.style([ 'no-fill' ], { strokeWidth: 5, stroke: 'fuchsia' });
        this.SHAPE_STYLE = styles.style({ fill: 'white', stroke: 'fuchsia', strokeWidth: 2 });
        this.FRAME_STYLE = styles.style([ 'no-fill' ], { stroke: 'fuchsia', strokeDasharray: 4, strokeWidth: 2 });
    }

    inherits_browser(DefaultRenderer, BaseRenderer);


    DefaultRenderer.prototype.canRender = function() {
        return true;
    };

    DefaultRenderer.prototype.drawShape = function drawShape(visuals, element) {
        var rect = create('rect');

        attr$1(rect, {
            x: 0,
            y: 0,
            width: element.width || 0,
            height: element.height || 0
        });

        if (isFrameElement(element)) {
            attr$1(rect, this.FRAME_STYLE);
        } else {
            attr$1(rect, this.SHAPE_STYLE);
        }

        append(visuals, rect);

        return rect;
    };

    DefaultRenderer.prototype.drawConnection = function drawConnection(visuals, connection) {

        var line = createLine(connection.waypoints, this.CONNECTION_STYLE);
        append(visuals, line);

        return line;
    };

    DefaultRenderer.prototype.getShapePath = function getShapePath(shape) {

        var x = shape.x,
            y = shape.y,
            width = shape.width,
            height = shape.height;

        var shapePath = [
            ['M', x, y],
            ['l', width, 0],
            ['l', 0, height],
            ['l', -width, 0],
            ['z']
        ];

        return componentsToPath(shapePath);
    };

    DefaultRenderer.prototype.getConnectionPath = function getConnectionPath(connection) {
        var waypoints = connection.waypoints;

        var idx, point, connectionPath = [];

        for (idx = 0; (point = waypoints[idx]); idx++) {

            // take invisible docking into account
            // when creating the path
            point = point.original || point;

            connectionPath.push([ idx === 0 ? 'M' : 'L', point.x, point.y ]);
        }

        return componentsToPath(connectionPath);
    };


    DefaultRenderer.$inject = [ 'eventBus', 'styles' ];

    /**
     * A component that manages shape styles
     */
    function Styles() {

        var defaultTraits = {

            'no-fill': {
                fill: 'none'
            },
            'no-border': {
                strokeOpacity: 0.0
            },
            'no-events': {
                pointerEvents: 'none'
            }
        };

        var self = this;

        /**
         * Builds a style definition from a className, a list of traits and an object of additional attributes.
         *
         * @param  {String} className
         * @param  {Array<String>} traits
         * @param  {Object} additionalAttrs
         *
         * @return {Object} the style defintion
         */
        this.cls = function(className, traits, additionalAttrs) {
            var attrs = this.style(traits, additionalAttrs);

            return assign(attrs, { 'class': className });
        };

        /**
         * Builds a style definition from a list of traits and an object of additional attributes.
         *
         * @param  {Array<String>} traits
         * @param  {Object} additionalAttrs
         *
         * @return {Object} the style defintion
         */
        this.style = function(traits, additionalAttrs) {

            if (!isArray(traits) && !additionalAttrs) {
                additionalAttrs = traits;
                traits = [];
            }

            var attrs = reduce(traits, function(attrs, t) {
                return assign(attrs, defaultTraits[t] || {});
            }, {});

            return additionalAttrs ? assign(attrs, additionalAttrs) : attrs;
        };

        this.computeStyle = function(custom, traits, defaultStyles) {
            if (!isArray(traits)) {
                defaultStyles = traits;
                traits = [];
            }

            return self.style(traits || [], assign({}, defaultStyles, custom || {}));
        };
    }

    var DrawModule = {
        __init__: [ 'defaultRenderer' ],
        defaultRenderer: [ 'type', DefaultRenderer ],
        styles: [ 'type', Styles ]
    };

    /**
     * Failsafe remove an element from a collection
     *
     * @param  {Array<Object>} [collection]
     * @param  {Object} [element]
     *
     * @return {Number} the previous index of the element
     */
    function remove$2(collection, element) {

        if (!collection || !element) {
            return -1;
        }

        var idx = collection.indexOf(element);

        if (idx !== -1) {
            collection.splice(idx, 1);
        }

        return idx;
    }

    /**
     * Fail save add an element to the given connection, ensuring
     * it does not yet exist.
     *
     * @param {Array<Object>} collection
     * @param {Object} element
     * @param {Number} idx
     */
    function add(collection, element, idx) {

        if (!collection || !element) {
            return;
        }

        if (typeof idx !== 'number') {
            idx = -1;
        }

        var currentIdx = collection.indexOf(element);

        if (currentIdx !== -1) {

            if (currentIdx === idx) {
                // nothing to do, position has not changed
                return;
            } else {

                if (idx !== -1) {
                    // remove from current position
                    collection.splice(currentIdx, 1);
                } else {
                    // already exists in collection
                    return;
                }
            }
        }

        if (idx !== -1) {
            // insert at specified position
            collection.splice(idx, 0, element);
        } else {
            // push to end
            collection.push(element);
        }
    }

    function round(number, resolution) {
        return Math.round(number * resolution) / resolution;
    }

    function ensurePx(number) {
        return isNumber(number) ? number + 'px' : number;
    }

    /**
     * Creates a HTML container element for a SVG element with
     * the given configuration
     *
     * @param  {Object} options
     * @return {HTMLElement} the container element
     */
    function createContainer(options) {

        options = assign({}, { width: '100%', height: '100%' }, options);

        var container = options.container || document.body;

        // create a <div> around the svg element with the respective size
        // this way we can always get the correct container size
        // (this is impossible for <svg> elements at the moment)
        var parent = document.createElement('div');
        parent.setAttribute('class', 'djs-container');

        assign(parent.style, {
            position: 'relative',
            overflow: 'hidden',
            width: ensurePx(options.width),
            height: ensurePx(options.height)
        });

        container.appendChild(parent);

        return parent;
    }

    function createGroup(parent, cls, childIndex) {
        var group = create('g');
        classes$1(group).add(cls);

        var index = childIndex !== undefined ? childIndex : parent.childNodes.length - 1;

        // must ensure second argument is node or _null_
        // cf. https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore
        parent.insertBefore(group, parent.childNodes[index] || null);

        return group;
    }

    var BASE_LAYER = 'base';


    var REQUIRED_MODEL_ATTRS = {
        shape: [ 'x', 'y', 'width', 'height' ],
        connection: [ 'waypoints' ]
    };

    /**
     * The main drawing canvas.
     *
     * @class
     * @constructor
     *
     * @emits Canvas#canvas.init
     *
     * @param {Object} config
     * @param {EventBus} eventBus
     * @param {GraphicsFactory} graphicsFactory
     * @param {ElementRegistry} elementRegistry
     */
    function Canvas(config, eventBus, graphicsFactory, elementRegistry) {

        this._eventBus = eventBus;
        this._elementRegistry = elementRegistry;
        this._graphicsFactory = graphicsFactory;

        this._init(config || {});
    }

    Canvas.$inject = [
        'config.canvas',
        'eventBus',
        'graphicsFactory',
        'elementRegistry'
    ];


    Canvas.prototype._init = function(config) {

        var eventBus = this._eventBus;

        // Creates a <svg> element that is wrapped into a <div>.
        // This way we are always able to correctly figure out the size of the svg element
        // by querying the parent node.
        //
        // (It is not possible to get the size of a svg element cross browser @ 2014-04-01)
        //
        // <div class="djs-container" style="width: {desired-width}, height: {desired-height}">
        //   <svg width="100%" height="100%">
        //    ...
        //   </svg>
        // </div>

        // html container
        var container = this._container = createContainer(config);

        var svg = this._svg = create('svg');
        attr$1(svg, { width: '100%', height: '100%' });

        append(container, svg);

        var viewport = this._viewport = createGroup(svg, 'viewport');

        this._layers = {};

        // debounce canvas.viewbox.changed events
        // for smoother diagram interaction
        if (config.deferUpdate !== false) {
            this._viewboxChanged = debounce(bind(this._viewboxChanged, this), 300);
        }

        eventBus.on('diagram.init', function() {

            /**
             * An event indicating that the canvas is ready to be drawn on.
             *
             * @memberOf Canvas
             *
             * @event canvas.init
             *
             * @type {Object}
             * @property {SVGElement} svg the created svg element
             * @property {SVGElement} viewport the direct parent of diagram elements and shapes
             */
            eventBus.fire('canvas.init', {
                svg: svg,
                viewport: viewport
            });

        }, this);

        // reset viewbox on shape changes to
        // recompute the viewbox
        eventBus.on([
            'shape.added',
            'connection.added',
            'shape.removed',
            'connection.removed',
            'elements.changed'
        ], function() {
            delete this._cachedViewbox;
        }, this);

        eventBus.on('diagram.destroy', 500, this._destroy, this);
        eventBus.on('diagram.clear', 500, this._clear, this);
    };

    Canvas.prototype._destroy = function(emit) {
        this._eventBus.fire('canvas.destroy', {
            svg: this._svg,
            viewport: this._viewport
        });

        var parent = this._container.parentNode;

        if (parent) {
            parent.removeChild(this._container);
        }

        delete this._svg;
        delete this._container;
        delete this._layers;
        delete this._rootElement;
        delete this._viewport;
    };

    Canvas.prototype._clear = function() {

        var self = this;

        var allElements = this._elementRegistry.getAll();

        // remove all elements
        allElements.forEach(function(element) {
            var type = getType(element);

            if (type === 'root') {
                self.setRootElement(null, true);
            } else {
                self._removeElement(element, type);
            }
        });

        // force recomputation of view box
        delete this._cachedViewbox;
    };

    /**
     * Returns the default layer on which
     * all elements are drawn.
     *
     * @returns {SVGElement}
     */
    Canvas.prototype.getDefaultLayer = function() {
        return this.getLayer(BASE_LAYER, 0);
    };

    /**
     * Returns a layer that is used to draw elements
     * or annotations on it.
     *
     * Non-existing layers retrieved through this method
     * will be created. During creation, the optional index
     * may be used to create layers below or above existing layers.
     * A layer with a certain index is always created above all
     * existing layers with the same index.
     *
     * @param {String} name
     * @param {Number} index
     *
     * @returns {SVGElement}
     */
    Canvas.prototype.getLayer = function(name, index) {

        if (!name) {
            throw new Error('must specify a name');
        }

        var layer = this._layers[name];

        if (!layer) {
            layer = this._layers[name] = this._createLayer(name, index);
        }

        // throw an error if layer creation / retrival is
        // requested on different index
        if (typeof index !== 'undefined' && layer.index !== index) {
            throw new Error('layer <' + name + '> already created at index <' + index + '>');
        }

        return layer.group;
    };

    /**
     * Creates a given layer and returns it.
     *
     * @param {String} name
     * @param {Number} [index=0]
     *
     * @return {Object} layer descriptor with { index, group: SVGGroup }
     */
    Canvas.prototype._createLayer = function(name, index) {

        if (!index) {
            index = 0;
        }

        var childIndex = reduce(this._layers, function(childIndex, layer) {
            if (index >= layer.index) {
                childIndex++;
            }

            return childIndex;
        }, 0);

        return {
            group: createGroup(this._viewport, 'layer-' + name, childIndex),
            index: index
        };

    };

    /**
     * Returns the html element that encloses the
     * drawing canvas.
     *
     * @return {DOMNode}
     */
    Canvas.prototype.getContainer = function() {
        return this._container;
    };


    // markers //////////////////////

    Canvas.prototype._updateMarker = function(element, marker, add) {
        var container;

        if (!element.id) {
            element = this._elementRegistry.get(element);
        }

        // we need to access all
        container = this._elementRegistry._elements[element.id];

        if (!container) {
            return;
        }

        forEach([ container.gfx, container.secondaryGfx ], function(gfx) {
            if (gfx) {
                // invoke either addClass or removeClass based on mode
                if (add) {
                    classes$1(gfx).add(marker);
                } else {
                    classes$1(gfx).remove(marker);
                }
            }
        });

        /**
         * An event indicating that a marker has been updated for an element
         *
         * @event element.marker.update
         * @type {Object}
         * @property {djs.model.Element} element the shape
         * @property {Object} gfx the graphical representation of the shape
         * @property {String} marker
         * @property {Boolean} add true if the marker was added, false if it got removed
         */
        this._eventBus.fire('element.marker.update', { element: element, gfx: container.gfx, marker: marker, add: !!add });
    };


    /**
     * Adds a marker to an element (basically a css class).
     *
     * Fires the element.marker.update event, making it possible to
     * integrate extension into the marker life-cycle, too.
     *
     * @example
     * canvas.addMarker('foo', 'some-marker');
     *
     * var fooGfx = canvas.getGraphics('foo');
     *
     * fooGfx; // <g class="... some-marker"> ... </g>
     *
     * @param {String|djs.model.Base} element
     * @param {String} marker
     */
    Canvas.prototype.addMarker = function(element, marker) {
        this._updateMarker(element, marker, true);
    };


    /**
     * Remove a marker from an element.
     *
     * Fires the element.marker.update event, making it possible to
     * integrate extension into the marker life-cycle, too.
     *
     * @param  {String|djs.model.Base} element
     * @param  {String} marker
     */
    Canvas.prototype.removeMarker = function(element, marker) {
        this._updateMarker(element, marker, false);
    };

    /**
     * Check the existence of a marker on element.
     *
     * @param  {String|djs.model.Base} element
     * @param  {String} marker
     */
    Canvas.prototype.hasMarker = function(element, marker) {
        if (!element.id) {
            element = this._elementRegistry.get(element);
        }

        var gfx = this.getGraphics(element);

        return classes$1(gfx).has(marker);
    };

    /**
     * Toggles a marker on an element.
     *
     * Fires the element.marker.update event, making it possible to
     * integrate extension into the marker life-cycle, too.
     *
     * @param  {String|djs.model.Base} element
     * @param  {String} marker
     */
    Canvas.prototype.toggleMarker = function(element, marker) {
        if (this.hasMarker(element, marker)) {
            this.removeMarker(element, marker);
        } else {
            this.addMarker(element, marker);
        }
    };

    Canvas.prototype.getRootElement = function() {
        if (!this._rootElement) {
            this.setRootElement({ id: '__implicitroot', children: [] });
        }

        return this._rootElement;
    };



    // root element handling //////////////////////

    /**
     * Sets a given element as the new root element for the canvas
     * and returns the new root element.
     *
     * @param {Object|djs.model.Root} element
     * @param {Boolean} [override] whether to override the current root element, if any
     *
     * @return {Object|djs.model.Root} new root element
     */
    Canvas.prototype.setRootElement = function(element, override) {

        if (element) {
            this._ensureValid('root', element);
        }

        var currentRoot = this._rootElement,
            elementRegistry = this._elementRegistry,
            eventBus = this._eventBus;

        if (currentRoot) {
            if (!override) {
                throw new Error('rootElement already set, need to specify override');
            }

            // simulate element remove event sequence
            eventBus.fire('root.remove', { element: currentRoot });
            eventBus.fire('root.removed', { element: currentRoot });

            elementRegistry.remove(currentRoot);
        }

        if (element) {
            var gfx = this.getDefaultLayer();

            // resemble element add event sequence
            eventBus.fire('root.add', { element: element });

            elementRegistry.add(element, gfx, this._svg);

            eventBus.fire('root.added', { element: element, gfx: gfx });
        }

        this._rootElement = element;

        return element;
    };



    // add functionality //////////////////////

    Canvas.prototype._ensureValid = function(type, element) {
        if (!element.id) {
            throw new Error('element must have an id');
        }

        if (this._elementRegistry.get(element.id)) {
            throw new Error('element with id ' + element.id + ' already exists');
        }

        var requiredAttrs = REQUIRED_MODEL_ATTRS[type];

        var valid = every(requiredAttrs, function(attr) {
            return typeof element[attr] !== 'undefined';
        });

        if (!valid) {
            throw new Error(
                'must supply { ' + requiredAttrs.join(', ') + ' } with ' + type);
        }
    };

    Canvas.prototype._setParent = function(element, parent, parentIndex) {
        add(parent.children, element, parentIndex);
        element.parent = parent;
    };

    /**
     * Adds an element to the canvas.
     *
     * This wires the parent <-> child relationship between the element and
     * a explicitly specified parent or an implicit root element.
     *
     * During add it emits the events
     *
     *  * <{type}.add> (element, parent)
     *  * <{type}.added> (element, gfx)
     *
     * Extensions may hook into these events to perform their magic.
     *
     * @param {String} type
     * @param {Object|djs.model.Base} element
     * @param {Object|djs.model.Base} [parent]
     * @param {Number} [parentIndex]
     *
     * @return {Object|djs.model.Base} the added element
     */
    Canvas.prototype._addElement = function(type, element, parent, parentIndex) {

        parent = parent || this.getRootElement();

        var eventBus = this._eventBus,
            graphicsFactory = this._graphicsFactory;

        this._ensureValid(type, element);

        eventBus.fire(type + '.add', { element: element, parent: parent });

        this._setParent(element, parent, parentIndex);

        // create graphics
        var gfx = graphicsFactory.create(type, element, parentIndex);

        this._elementRegistry.add(element, gfx);

        // update its visual
        graphicsFactory.update(type, element, gfx);

        eventBus.fire(type + '.added', { element: element, gfx: gfx });

        return element;
    };

    /**
     * Adds a shape to the canvas
     *
     * @param {Object|djs.model.Shape} shape to add to the diagram
     * @param {djs.model.Base} [parent]
     * @param {Number} [parentIndex]
     *
     * @return {djs.model.Shape} the added shape
     */
    Canvas.prototype.addShape = function(shape, parent, parentIndex) {
        return this._addElement('shape', shape, parent, parentIndex);
    };

    /**
     * Adds a connection to the canvas
     *
     * @param {Object|djs.model.Connection} connection to add to the diagram
     * @param {djs.model.Base} [parent]
     * @param {Number} [parentIndex]
     *
     * @return {djs.model.Connection} the added connection
     */
    Canvas.prototype.addConnection = function(connection, parent, parentIndex) {
        return this._addElement('connection', connection, parent, parentIndex);
    };


    /**
     * Internal remove element
     */
    Canvas.prototype._removeElement = function(element, type) {

        var elementRegistry = this._elementRegistry,
            graphicsFactory = this._graphicsFactory,
            eventBus = this._eventBus;

        element = elementRegistry.get(element.id || element);

        if (!element) {
            // element was removed already
            return;
        }

        eventBus.fire(type + '.remove', { element: element });

        graphicsFactory.remove(element);

        // unset parent <-> child relationship
        remove$2(element.parent && element.parent.children, element);
        element.parent = null;

        eventBus.fire(type + '.removed', { element: element });

        elementRegistry.remove(element);

        return element;
    };


    /**
     * Removes a shape from the canvas
     *
     * @param {String|djs.model.Shape} shape or shape id to be removed
     *
     * @return {djs.model.Shape} the removed shape
     */
    Canvas.prototype.removeShape = function(shape) {

        /**
         * An event indicating that a shape is about to be removed from the canvas.
         *
         * @memberOf Canvas
         *
         * @event shape.remove
         * @type {Object}
         * @property {djs.model.Shape} element the shape descriptor
         * @property {Object} gfx the graphical representation of the shape
         */

        /**
         * An event indicating that a shape has been removed from the canvas.
         *
         * @memberOf Canvas
         *
         * @event shape.removed
         * @type {Object}
         * @property {djs.model.Shape} element the shape descriptor
         * @property {Object} gfx the graphical representation of the shape
         */
        return this._removeElement(shape, 'shape');
    };


    /**
     * Removes a connection from the canvas
     *
     * @param {String|djs.model.Connection} connection or connection id to be removed
     *
     * @return {djs.model.Connection} the removed connection
     */
    Canvas.prototype.removeConnection = function(connection) {

        /**
         * An event indicating that a connection is about to be removed from the canvas.
         *
         * @memberOf Canvas
         *
         * @event connection.remove
         * @type {Object}
         * @property {djs.model.Connection} element the connection descriptor
         * @property {Object} gfx the graphical representation of the connection
         */

        /**
         * An event indicating that a connection has been removed from the canvas.
         *
         * @memberOf Canvas
         *
         * @event connection.removed
         * @type {Object}
         * @property {djs.model.Connection} element the connection descriptor
         * @property {Object} gfx the graphical representation of the connection
         */
        return this._removeElement(connection, 'connection');
    };


    /**
     * Return the graphical object underlaying a certain diagram element
     *
     * @param {String|djs.model.Base} element descriptor of the element
     * @param {Boolean} [secondary=false] whether to return the secondary connected element
     *
     * @return {SVGElement}
     */
    Canvas.prototype.getGraphics = function(element, secondary) {
        return this._elementRegistry.getGraphics(element, secondary);
    };


    /**
     * Perform a viewbox update via a given change function.
     *
     * @param {Function} changeFn
     */
    Canvas.prototype._changeViewbox = function(changeFn) {

        // notify others of the upcoming viewbox change
        this._eventBus.fire('canvas.viewbox.changing');

        // perform actual change
        changeFn.apply(this);

        // reset the cached viewbox so that
        // a new get operation on viewbox or zoom
        // triggers a viewbox re-computation
        this._cachedViewbox = null;

        // notify others of the change; this step
        // may or may not be debounced
        this._viewboxChanged();
    };

    Canvas.prototype._viewboxChanged = function() {
        this._eventBus.fire('canvas.viewbox.changed', { viewbox: this.viewbox() });
    };


    /**
     * Gets or sets the view box of the canvas, i.e. the
     * area that is currently displayed.
     *
     * The getter may return a cached viewbox (if it is currently
     * changing). To force a recomputation, pass `false` as the first argument.
     *
     * @example
     *
     * canvas.viewbox({ x: 100, y: 100, width: 500, height: 500 })
     *
     * // sets the visible area of the diagram to (100|100) -> (600|100)
     * // and and scales it according to the diagram width
     *
     * var viewbox = canvas.viewbox(); // pass `false` to force recomputing the box.
     *
     * console.log(viewbox);
     * // {
     * //   inner: Dimensions,
     * //   outer: Dimensions,
     * //   scale,
     * //   x, y,
     * //   width, height
     * // }
     *
     * // if the current diagram is zoomed and scrolled, you may reset it to the
     * // default zoom via this method, too:
     *
     * var zoomedAndScrolledViewbox = canvas.viewbox();
     *
     * canvas.viewbox({
     *   x: 0,
     *   y: 0,
     *   width: zoomedAndScrolledViewbox.outer.width,
     *   height: zoomedAndScrolledViewbox.outer.height
     * });
     *
     * @param  {Object} [box] the new view box to set
     * @param  {Number} box.x the top left X coordinate of the canvas visible in view box
     * @param  {Number} box.y the top left Y coordinate of the canvas visible in view box
     * @param  {Number} box.width the visible width
     * @param  {Number} box.height
     *
     * @return {Object} the current view box
     */
    Canvas.prototype.viewbox = function(box) {

        if (box === undefined && this._cachedViewbox) {
            return this._cachedViewbox;
        }

        var viewport = this._viewport,
            innerBox,
            outerBox = this.getSize(),
            matrix,
            transform$1,
            scale,
            x, y;

        if (!box) {
            // compute the inner box based on the
            // diagrams default layer. This allows us to exclude
            // external components, such as overlays
            innerBox = this.getDefaultLayer().getBBox();

            transform$1 = transform(viewport);
            matrix = transform$1 ? transform$1.matrix : createMatrix();
            scale = round(matrix.a, 1000);

            x = round(-matrix.e || 0, 1000);
            y = round(-matrix.f || 0, 1000);

            box = this._cachedViewbox = {
                x: x ? x / scale : 0,
                y: y ? y / scale : 0,
                width: outerBox.width / scale,
                height: outerBox.height / scale,
                scale: scale,
                inner: {
                    width: innerBox.width,
                    height: innerBox.height,
                    x: innerBox.x,
                    y: innerBox.y
                },
                outer: outerBox
            };

            return box;
        } else {

            this._changeViewbox(function() {
                scale = Math.min(outerBox.width / box.width, outerBox.height / box.height);

                var matrix = this._svg.createSVGMatrix()
                    .scale(scale)
                    .translate(-box.x, -box.y);

                transform(viewport, matrix);
            });
        }

        return box;
    };


    /**
     * Gets or sets the scroll of the canvas.
     *
     * @param {Object} [delta] the new scroll to apply.
     *
     * @param {Number} [delta.dx]
     * @param {Number} [delta.dy]
     */
    Canvas.prototype.scroll = function(delta) {

        var node = this._viewport;
        var matrix = node.getCTM();

        if (delta) {
            this._changeViewbox(function() {
                delta = assign({ dx: 0, dy: 0 }, delta || {});

                matrix = this._svg.createSVGMatrix().translate(delta.dx, delta.dy).multiply(matrix);

                setCTM(node, matrix);
            });
        }

        return { x: matrix.e, y: matrix.f };
    };


    /**
     * Gets or sets the current zoom of the canvas, optionally zooming
     * to the specified position.
     *
     * The getter may return a cached zoom level. Call it with `false` as
     * the first argument to force recomputation of the current level.
     *
     * @param {String|Number} [newScale] the new zoom level, either a number, i.e. 0.9,
     *                                   or `fit-viewport` to adjust the size to fit the current viewport
     * @param {String|Point} [center] the reference point { x: .., y: ..} to zoom to, 'auto' to zoom into mid or null
     *
     * @return {Number} the current scale
     */
    Canvas.prototype.zoom = function(newScale, center) {

        if (!newScale) {
            return this.viewbox(newScale).scale;
        }

        if (newScale === 'fit-viewport') {
            return this._fitViewport(center);
        }

        var outer,
            matrix;

        this._changeViewbox(function() {

            if (typeof center !== 'object') {
                outer = this.viewbox().outer;

                center = {
                    x: outer.width / 2,
                    y: outer.height / 2
                };
            }

            matrix = this._setZoom(newScale, center);
        });

        return round(matrix.a, 1000);
    };

    function setCTM(node, m) {
        var mstr = 'matrix(' + m.a + ',' + m.b + ',' + m.c + ',' + m.d + ',' + m.e + ',' + m.f + ')';
        node.setAttribute('transform', mstr);
    }

    Canvas.prototype._fitViewport = function(center) {

        var vbox = this.viewbox(),
            outer = vbox.outer,
            inner = vbox.inner,
            newScale,
            newViewbox;

        // display the complete diagram without zooming in.
        // instead of relying on internal zoom, we perform a
        // hard reset on the canvas viewbox to realize this
        //
        // if diagram does not need to be zoomed in, we focus it around
        // the diagram origin instead

        if (inner.x >= 0 &&
            inner.y >= 0 &&
            inner.x + inner.width <= outer.width &&
            inner.y + inner.height <= outer.height &&
            !center) {

            newViewbox = {
                x: 0,
                y: 0,
                width: Math.max(inner.width + inner.x, outer.width),
                height: Math.max(inner.height + inner.y, outer.height)
            };
        } else {

            newScale = Math.min(1, outer.width / inner.width, outer.height / inner.height);
            newViewbox = {
                x: inner.x + (center ? inner.width / 2 - outer.width / newScale / 2 : 0),
                y: inner.y + (center ? inner.height / 2 - outer.height / newScale / 2 : 0),
                width: outer.width / newScale,
                height: outer.height / newScale
            };
        }

        this.viewbox(newViewbox);

        return this.viewbox(false).scale;
    };


    Canvas.prototype._setZoom = function(scale, center) {

        var svg = this._svg,
            viewport = this._viewport;

        var matrix = svg.createSVGMatrix();
        var point = svg.createSVGPoint();

        var centerPoint,
            originalPoint,
            currentMatrix,
            scaleMatrix,
            newMatrix;

        currentMatrix = viewport.getCTM();

        var currentScale = currentMatrix.a;

        if (center) {
            centerPoint = assign(point, center);

            // revert applied viewport transformations
            originalPoint = centerPoint.matrixTransform(currentMatrix.inverse());

            // create scale matrix
            scaleMatrix = matrix
                .translate(originalPoint.x, originalPoint.y)
                .scale(1 / currentScale * scale)
                .translate(-originalPoint.x, -originalPoint.y);

            newMatrix = currentMatrix.multiply(scaleMatrix);
        } else {
            newMatrix = matrix.scale(scale);
        }

        setCTM(this._viewport, newMatrix);

        return newMatrix;
    };


    /**
     * Returns the size of the canvas
     *
     * @return {Dimensions}
     */
    Canvas.prototype.getSize = function() {
        return {
            width: this._container.clientWidth,
            height: this._container.clientHeight
        };
    };


    /**
     * Return the absolute bounding box for the given element
     *
     * The absolute bounding box may be used to display overlays in the
     * callers (browser) coordinate system rather than the zoomed in/out
     * canvas coordinates.
     *
     * @param  {ElementDescriptor} element
     * @return {Bounds} the absolute bounding box
     */
    Canvas.prototype.getAbsoluteBBox = function(element) {
        var vbox = this.viewbox();
        var bbox;

        // connection
        // use svg bbox
        if (element.waypoints) {
            var gfx = this.getGraphics(element);

            bbox = gfx.getBBox();
        }
        // shapes
        // use data
        else {
            bbox = element;
        }

        var x = bbox.x * vbox.scale - vbox.x * vbox.scale;
        var y = bbox.y * vbox.scale - vbox.y * vbox.scale;

        var width = bbox.width * vbox.scale;
        var height = bbox.height * vbox.scale;

        return {
            x: x,
            y: y,
            width: width,
            height: height
        };
    };

    /**
     * Fires an event in order other modules can react to the
     * canvas resizing
     */
    Canvas.prototype.resized = function() {

        // force recomputation of view box
        delete this._cachedViewbox;

        this._eventBus.fire('canvas.resized');
    };

    var ELEMENT_ID = 'data-element-id';


    /**
     * @class
     *
     * A registry that keeps track of all shapes in the diagram.
     */
    function ElementRegistry(eventBus) {
        this._elements = {};

        this._eventBus = eventBus;
    }

    ElementRegistry.$inject = [ 'eventBus' ];

    /**
     * Register a pair of (element, gfx, (secondaryGfx)).
     *
     * @param {djs.model.Base} element
     * @param {SVGElement} gfx
     * @param {SVGElement} [secondaryGfx] optional other element to register, too
     */
    ElementRegistry.prototype.add = function(element, gfx, secondaryGfx) {

        var id = element.id;

        this._validateId(id);

        // associate dom node with element
        attr$1(gfx, ELEMENT_ID, id);

        if (secondaryGfx) {
            attr$1(secondaryGfx, ELEMENT_ID, id);
        }

        this._elements[id] = { element: element, gfx: gfx, secondaryGfx: secondaryGfx };
    };

    /**
     * Removes an element from the registry.
     *
     * @param {djs.model.Base} element
     */
    ElementRegistry.prototype.remove = function(element) {
        var elements = this._elements,
            id = element.id || element,
            container = id && elements[id];

        if (container) {

            // unset element id on gfx
            attr$1(container.gfx, ELEMENT_ID, '');

            if (container.secondaryGfx) {
                attr$1(container.secondaryGfx, ELEMENT_ID, '');
            }

            delete elements[id];
        }
    };

    /**
     * Update the id of an element
     *
     * @param {djs.model.Base} element
     * @param {String} newId
     */
    ElementRegistry.prototype.updateId = function(element, newId) {

        this._validateId(newId);

        if (typeof element === 'string') {
            element = this.get(element);
        }

        this._eventBus.fire('element.updateId', {
            element: element,
            newId: newId
        });

        var gfx = this.getGraphics(element),
            secondaryGfx = this.getGraphics(element, true);

        this.remove(element);

        element.id = newId;

        this.add(element, gfx, secondaryGfx);
    };

    /**
     * Return the model element for a given id or graphics.
     *
     * @example
     *
     * elementRegistry.get('SomeElementId_1');
     * elementRegistry.get(gfx);
     *
     *
     * @param {String|SVGElement} filter for selecting the element
     *
     * @return {djs.model.Base}
     */
    ElementRegistry.prototype.get = function(filter) {
        var id;

        if (typeof filter === 'string') {
            id = filter;
        } else {
            id = filter && attr$1(filter, ELEMENT_ID);
        }

        var container = this._elements[id];
        return container && container.element;
    };

    /**
     * Return all elements that match a given filter function.
     *
     * @param {Function} fn
     *
     * @return {Array<djs.model.Base>}
     */
    ElementRegistry.prototype.filter = function(fn) {

        var filtered = [];

        this.forEach(function(element, gfx) {
            if (fn(element, gfx)) {
                filtered.push(element);
            }
        });

        return filtered;
    };

    /**
     * Return all rendered model elements.
     *
     * @return {Array<djs.model.Base>}
     */
    ElementRegistry.prototype.getAll = function() {
        return this.filter(function(e) { return e; });
    };

    /**
     * Iterate over all diagram elements.
     *
     * @param {Function} fn
     */
    ElementRegistry.prototype.forEach = function(fn) {

        var map = this._elements;

        Object.keys(map).forEach(function(id) {
            var container = map[id],
                element = container.element,
                gfx = container.gfx;

            return fn(element, gfx);
        });
    };

    /**
     * Return the graphical representation of an element or its id.
     *
     * @example
     * elementRegistry.getGraphics('SomeElementId_1');
     * elementRegistry.getGraphics(rootElement); // <g ...>
     *
     * elementRegistry.getGraphics(rootElement, true); // <svg ...>
     *
     *
     * @param {String|djs.model.Base} filter
     * @param {Boolean} [secondary=false] whether to return the secondary connected element
     *
     * @return {SVGElement}
     */
    ElementRegistry.prototype.getGraphics = function(filter, secondary) {
        var id = filter.id || filter;

        var container = this._elements[id];
        return container && (secondary ? container.secondaryGfx : container.gfx);
    };

    /**
     * Validate the suitability of the given id and signals a problem
     * with an exception.
     *
     * @param {String} id
     *
     * @throws {Error} if id is empty or already assigned
     */
    ElementRegistry.prototype._validateId = function(id) {
        if (!id) {
            throw new Error('element must have an id');
        }

        if (this._elements[id]) {
            throw new Error('element with id ' + id + ' already added');
        }
    };

    /**
     * An empty collection stub. Use {@link RefsCollection.extend} to extend a
     * collection with ref semantics.
     *
     * @class RefsCollection
     */

    /**
     * Extends a collection with {@link Refs} aware methods
     *
     * @memberof RefsCollection
     * @static
     *
     * @param  {Array<Object>} collection
     * @param  {Refs} refs instance
     * @param  {Object} property represented by the collection
     * @param  {Object} target object the collection is attached to
     *
     * @return {RefsCollection<Object>} the extended array
     */
    function extend$1(collection, refs, property, target) {

        var inverseProperty = property.inverse;

        /**
         * Removes the given element from the array and returns it.
         *
         * @method RefsCollection#remove
         *
         * @param {Object} element the element to remove
         */
        Object.defineProperty(collection, 'remove', {
            value: function(element) {
                var idx = this.indexOf(element);
                if (idx !== -1) {
                    this.splice(idx, 1);

                    // unset inverse
                    refs.unset(element, inverseProperty, target);
                }

                return element;
            }
        });

        /**
         * Returns true if the collection contains the given element
         *
         * @method RefsCollection#contains
         *
         * @param {Object} element the element to check for
         */
        Object.defineProperty(collection, 'contains', {
            value: function(element) {
                return this.indexOf(element) !== -1;
            }
        });

        /**
         * Adds an element to the array, unless it exists already (set semantics).
         *
         * @method RefsCollection#add
         *
         * @param {Object} element the element to add
         * @param {Number} optional index to add element to
         *                 (possibly moving other elements around)
         */
        Object.defineProperty(collection, 'add', {
            value: function(element, idx) {

                var currentIdx = this.indexOf(element);

                if (typeof idx === 'undefined') {

                    if (currentIdx !== -1) {
                        // element already in collection (!)
                        return;
                    }

                    // add to end of array, as no idx is specified
                    idx = this.length;
                }

                // handle already in collection
                if (currentIdx !== -1) {

                    // remove element from currentIdx
                    this.splice(currentIdx, 1);
                }

                // add element at idx
                this.splice(idx, 0, element);

                if (currentIdx === -1) {
                    // set inverse, unless element was
                    // in collection already
                    refs.set(element, inverseProperty, target);
                }
            }
        });

        // a simple marker, identifying this element
        // as being a refs collection
        Object.defineProperty(collection, '__refs_collection', {
            value: true
        });

        return collection;
    }


    function isExtended(collection) {
        return collection.__refs_collection === true;
    }

    var extend_1 = extend$1;

    var isExtended_1 = isExtended;

    var collection = {
        extend: extend_1,
        isExtended: isExtended_1
    };

    function hasOwnProperty(e, property) {
        return Object.prototype.hasOwnProperty.call(e, property.name || property);
    }

    function defineCollectionProperty(ref, property, target) {

        var collection$1 = collection.extend(target[property.name] || [], ref, property, target);

        Object.defineProperty(target, property.name, {
            enumerable: property.enumerable,
            value: collection$1
        });

        if (collection$1.length) {

            collection$1.forEach(function(o) {
                ref.set(o, property.inverse, target);
            });
        }
    }


    function defineProperty(ref, property, target) {

        var inverseProperty = property.inverse;

        var _value = target[property.name];

        Object.defineProperty(target, property.name, {
            configurable: property.configurable,
            enumerable: property.enumerable,

            get: function() {
                return _value;
            },

            set: function(value) {

                // return if we already performed all changes
                if (value === _value) {
                    return;
                }

                var old = _value;

                // temporary set null
                _value = null;

                if (old) {
                    ref.unset(old, inverseProperty, target);
                }

                // set new value
                _value = value;

                // set inverse value
                ref.set(_value, inverseProperty, target);
            }
        });

    }

    /**
     * Creates a new references object defining two inversly related
     * attribute descriptors a and b.
     *
     * <p>
     *   When bound to an object using {@link Refs#bind} the references
     *   get activated and ensure that add and remove operations are applied
     *   reversely, too.
     * </p>
     *
     * <p>
     *   For attributes represented as collections {@link Refs} provides the
     *   {@link RefsCollection#add}, {@link RefsCollection#remove} and {@link RefsCollection#contains} extensions
     *   that must be used to properly hook into the inverse change mechanism.
     * </p>
     *
     * @class Refs
     *
     * @classdesc A bi-directional reference between two attributes.
     *
     * @param {Refs.AttributeDescriptor} a property descriptor
     * @param {Refs.AttributeDescriptor} b property descriptor
     *
     * @example
     *
     * var refs = Refs({ name: 'wheels', collection: true, enumerable: true }, { name: 'car' });
     *
     * var car = { name: 'toyota' };
     * var wheels = [{ pos: 'front-left' }, { pos: 'front-right' }];
     *
     * refs.bind(car, 'wheels');
     *
     * car.wheels // []
     * car.wheels.add(wheels[0]);
     * car.wheels.add(wheels[1]);
     *
     * car.wheels // [{ pos: 'front-left' }, { pos: 'front-right' }]
     *
     * wheels[0].car // { name: 'toyota' };
     * car.wheels.remove(wheels[0]);
     *
     * wheels[0].car // undefined
     */
    function Refs(a, b) {

        if (!(this instanceof Refs)) {
            return new Refs(a, b);
        }

        // link
        a.inverse = b;
        b.inverse = a;

        this.props = {};
        this.props[a.name] = a;
        this.props[b.name] = b;
    }

    /**
     * Binds one side of a bi-directional reference to a
     * target object.
     *
     * @memberOf Refs
     *
     * @param  {Object} target
     * @param  {String} property
     */
    Refs.prototype.bind = function(target, property) {
        if (typeof property === 'string') {
            if (!this.props[property]) {
                throw new Error('no property <' + property + '> in ref');
            }
            property = this.props[property];
        }

        if (property.collection) {
            defineCollectionProperty(this, property, target);
        } else {
            defineProperty(this, property, target);
        }
    };

    Refs.prototype.ensureRefsCollection = function(target, property) {

        var collection$1 = target[property.name];

        if (!collection.isExtended(collection$1)) {
            defineCollectionProperty(this, property, target);
        }

        return collection$1;
    };

    Refs.prototype.ensureBound = function(target, property) {
        if (!hasOwnProperty(target, property)) {
            this.bind(target, property);
        }
    };

    Refs.prototype.unset = function(target, property, value) {

        if (target) {
            this.ensureBound(target, property);

            if (property.collection) {
                this.ensureRefsCollection(target, property).remove(value);
            } else {
                target[property.name] = undefined;
            }
        }
    };

    Refs.prototype.set = function(target, property, value) {

        if (target) {
            this.ensureBound(target, property);

            if (property.collection) {
                this.ensureRefsCollection(target, property).add(value);
            } else {
                target[property.name] = value;
            }
        }
    };

    var refs = Refs;

    var objectRefs = refs;

    var Collection = collection;
    objectRefs.Collection = Collection;

    var parentRefs = new objectRefs({ name: 'children', enumerable: true, collection: true }, { name: 'parent' }),
        labelRefs = new objectRefs({ name: 'labels', enumerable: true, collection: true }, { name: 'labelTarget' }),
        attacherRefs = new objectRefs({ name: 'attachers', collection: true }, { name: 'host' }),
        outgoingRefs = new objectRefs({ name: 'outgoing', collection: true }, { name: 'source' }),
        incomingRefs = new objectRefs({ name: 'incoming', collection: true }, { name: 'target' });

    /**
     * @namespace djs.model
     */

    /**
     * @memberOf djs.model
     */

    /**
     * The basic graphical representation
     *
     * @class
     *
     * @abstract
     */
    function Base() {

        /**
         * The object that backs up the shape
         *
         * @name Base#businessObject
         * @type Object
         */
        Object.defineProperty(this, 'businessObject', {
            writable: true
        });


        /**
         * Single label support, will mapped to multi label array
         *
         * @name Base#label
         * @type Object
         */
        Object.defineProperty(this, 'label', {
            get: function() {
                return this.labels[0];
            },
            set: function(newLabel) {

                var label = this.label,
                    labels = this.labels;

                if (!newLabel && label) {
                    labels.remove(label);
                } else {
                    labels.add(newLabel, 0);
                }
            }
        });

        /**
         * The parent shape
         *
         * @name Base#parent
         * @type Shape
         */
        parentRefs.bind(this, 'parent');

        /**
         * The list of labels
         *
         * @name Base#labels
         * @type Label
         */
        labelRefs.bind(this, 'labels');

        /**
         * The list of outgoing connections
         *
         * @name Base#outgoing
         * @type Array<Connection>
         */
        outgoingRefs.bind(this, 'outgoing');

        /**
         * The list of incoming connections
         *
         * @name Base#incoming
         * @type Array<Connection>
         */
        incomingRefs.bind(this, 'incoming');
    }


    /**
     * A graphical object
     *
     * @class
     * @constructor
     *
     * @extends Base
     */
    function Shape() {
        Base.call(this);

        /**
         * Indicates frame shapes
         *
         * @name Shape#isFrame
         * @type Boolean
         */

        /**
         * The list of children
         *
         * @name Shape#children
         * @type Array<Base>
         */
        parentRefs.bind(this, 'children');

        /**
         * @name Shape#host
         * @type Shape
         */
        attacherRefs.bind(this, 'host');

        /**
         * @name Shape#attachers
         * @type Shape
         */
        attacherRefs.bind(this, 'attachers');
    }

    inherits_browser(Shape, Base);


    /**
     * A root graphical object
     *
     * @class
     * @constructor
     *
     * @extends Shape
     */
    function Root() {
        Shape.call(this);
    }

    inherits_browser(Root, Shape);


    /**
     * A label for an element
     *
     * @class
     * @constructor
     *
     * @extends Shape
     */
    function Label() {
        Shape.call(this);

        /**
         * The labeled element
         *
         * @name Label#labelTarget
         * @type Base
         */
        labelRefs.bind(this, 'labelTarget');
    }

    inherits_browser(Label, Shape);


    /**
     * A connection between two elements
     *
     * @class
     * @constructor
     *
     * @extends Base
     */
    function Connection() {
        Base.call(this);

        /**
         * The element this connection originates from
         *
         * @name Connection#source
         * @type Base
         */
        outgoingRefs.bind(this, 'source');

        /**
         * The element this connection points to
         *
         * @name Connection#target
         * @type Base
         */
        incomingRefs.bind(this, 'target');
    }

    inherits_browser(Connection, Base);


    var types = {
        connection: Connection,
        shape: Shape,
        label: Label,
        root: Root
    };

    /**
     * Creates a new model element of the specified type
     *
     * @method create
     *
     * @example
     *
     * var shape1 = Model.create('shape', { x: 10, y: 10, width: 100, height: 100 });
     * var shape2 = Model.create('shape', { x: 210, y: 210, width: 100, height: 100 });
     *
     * var connection = Model.create('connection', { waypoints: [ { x: 110, y: 55 }, {x: 210, y: 55 } ] });
     *
     * @param  {String} type lower-cased model name
     * @param  {Object} attrs attributes to initialize the new model instance with
     *
     * @return {Base} the new model instance
     */
    function create$1(type, attrs) {
        var Type = types[type];
        if (!Type) {
            throw new Error('unknown type: <' + type + '>');
        }
        return assign(new Type(), attrs);
    }

    /**
     * A factory for diagram-js shapes
     */
    function ElementFactory() {
        this._uid = 12;
    }


    ElementFactory.prototype.createRoot = function(attrs) {
        return this.create('root', attrs);
    };

    ElementFactory.prototype.createLabel = function(attrs) {
        return this.create('label', attrs);
    };

    ElementFactory.prototype.createShape = function(attrs) {
        return this.create('shape', attrs);
    };

    ElementFactory.prototype.createConnection = function(attrs) {
        return this.create('connection', attrs);
    };

    /**
     * Create a model element with the given type and
     * a number of pre-set attributes.
     *
     * @param  {String} type
     * @param  {Object} attrs
     * @return {djs.model.Base} the newly created model instance
     */
    ElementFactory.prototype.create = function(type, attrs) {

        attrs = assign({}, attrs || {});

        if (!attrs.id) {
            attrs.id = type + '_' + (this._uid++);
        }

        return create$1(type, attrs);
    };

    var FN_REF = '__fn';

    var DEFAULT_PRIORITY = 1000;

    var slice$1 = Array.prototype.slice;

    /**
     * A general purpose event bus.
     *
     * This component is used to communicate across a diagram instance.
     * Other parts of a diagram can use it to listen to and broadcast events.
     *
     *
     * ## Registering for Events
     *
     * The event bus provides the {@link EventBus#on} and {@link EventBus#once}
     * methods to register for events. {@link EventBus#off} can be used to
     * remove event registrations. Listeners receive an instance of {@link Event}
     * as the first argument. It allows them to hook into the event execution.
     *
     * ```javascript
     *
     * // listen for event
     * eventBus.on('foo', function(event) {
     *
     *   // access event type
     *   event.type; // 'foo'
     *
     *   // stop propagation to other listeners
     *   event.stopPropagation();
     *
     *   // prevent event default
     *   event.preventDefault();
     * });
     *
     * // listen for event with custom payload
     * eventBus.on('bar', function(event, payload) {
     *   console.log(payload);
     * });
     *
     * // listen for event returning value
     * eventBus.on('foobar', function(event) {
     *
     *   // stop event propagation + prevent default
     *   return false;
     *
     *   // stop event propagation + return custom result
     *   return {
     *     complex: 'listening result'
     *   };
     * });
     *
     *
     * // listen with custom priority (default=1000, higher is better)
     * eventBus.on('priorityfoo', 1500, function(event) {
     *   console.log('invoked first!');
     * });
     *
     *
     * // listen for event and pass the context (`this`)
     * eventBus.on('foobar', function(event) {
     *   this.foo();
     * }, this);
     * ```
     *
     *
     * ## Emitting Events
     *
     * Events can be emitted via the event bus using {@link EventBus#fire}.
     *
     * ```javascript
     *
     * // false indicates that the default action
     * // was prevented by listeners
     * if (eventBus.fire('foo') === false) {
     *   console.log('default has been prevented!');
     * };
     *
     *
     * // custom args + return value listener
     * eventBus.on('sum', function(event, a, b) {
     *   return a + b;
     * });
     *
     * // you can pass custom arguments + retrieve result values.
     * var sum = eventBus.fire('sum', 1, 2);
     * console.log(sum); // 3
     * ```
     */
    function EventBus() {
        this._listeners = {};

        // cleanup on destroy on lowest priority to allow
        // message passing until the bitter end
        this.on('diagram.destroy', 1, this._destroy, this);
    }


    /**
     * Register an event listener for events with the given name.
     *
     * The callback will be invoked with `event, ...additionalArguments`
     * that have been passed to {@link EventBus#fire}.
     *
     * Returning false from a listener will prevent the events default action
     * (if any is specified). To stop an event from being processed further in
     * other listeners execute {@link Event#stopPropagation}.
     *
     * Returning anything but `undefined` from a listener will stop the listener propagation.
     *
     * @param {String|Array<String>} events
     * @param {Number} [priority=1000] the priority in which this listener is called, larger is higher
     * @param {Function} callback
     * @param {Object} [that] Pass context (`this`) to the callback
     */
    EventBus.prototype.on = function(events, priority, callback, that) {

        events = isArray(events) ? events : [ events ];

        if (isFunction(priority)) {
            that = callback;
            callback = priority;
            priority = DEFAULT_PRIORITY;
        }

        if (!isNumber(priority)) {
            throw new Error('priority must be a number');
        }

        var actualCallback = callback;

        if (that) {
            actualCallback = bind(callback, that);

            // make sure we remember and are able to remove
            // bound callbacks via {@link #off} using the original
            // callback
            actualCallback[FN_REF] = callback[FN_REF] || callback;
        }

        var self = this;

        events.forEach(function(e) {
            self._addListener(e, {
                priority: priority,
                callback: actualCallback,
                next: null
            });
        });
    };


    /**
     * Register an event listener that is executed only once.
     *
     * @param {String} event the event name to register for
     * @param {Number} [priority=1000] the priority in which this listener is called, larger is higher
     * @param {Function} callback the callback to execute
     * @param {Object} [that] Pass context (`this`) to the callback
     */
    EventBus.prototype.once = function(event, priority, callback, that) {
        var self = this;

        if (isFunction(priority)) {
            that = callback;
            callback = priority;
            priority = DEFAULT_PRIORITY;
        }

        if (!isNumber(priority)) {
            throw new Error('priority must be a number');
        }

        function wrappedCallback() {
            var result = callback.apply(that, arguments);

            self.off(event, wrappedCallback);

            return result;
        }

        // make sure we remember and are able to remove
        // bound callbacks via {@link #off} using the original
        // callback
        wrappedCallback[FN_REF] = callback;

        this.on(event, priority, wrappedCallback);
    };


    /**
     * Removes event listeners by event and callback.
     *
     * If no callback is given, all listeners for a given event name are being removed.
     *
     * @param {String|Array<String>} events
     * @param {Function} [callback]
     */
    EventBus.prototype.off = function(events, callback) {

        events = isArray(events) ? events : [ events ];

        var self = this;

        events.forEach(function(event) {
            self._removeListener(event, callback);
        });

    };


    /**
     * Create an EventBus event.
     *
     * @param {Object} data
     *
     * @return {Object} event, recognized by the eventBus
     */
    EventBus.prototype.createEvent = function(data) {
        var event = new InternalEvent();

        event.init(data);

        return event;
    };


    /**
     * Fires a named event.
     *
     * @example
     *
     * // fire event by name
     * events.fire('foo');
     *
     * // fire event object with nested type
     * var event = { type: 'foo' };
     * events.fire(event);
     *
     * // fire event with explicit type
     * var event = { x: 10, y: 20 };
     * events.fire('element.moved', event);
     *
     * // pass additional arguments to the event
     * events.on('foo', function(event, bar) {
     *   alert(bar);
     * });
     *
     * events.fire({ type: 'foo' }, 'I am bar!');
     *
     * @param {String} [name] the optional event name
     * @param {Object} [event] the event object
     * @param {...Object} additional arguments to be passed to the callback functions
     *
     * @return {Boolean} the events return value, if specified or false if the
     *                   default action was prevented by listeners
     */
    EventBus.prototype.fire = function(type, data) {

        var event,
            firstListener,
            returnValue,
            args;

        args = slice$1.call(arguments);

        if (typeof type === 'object') {
            event = type;
            type = event.type;
        }

        if (!type) {
            throw new Error('no event type specified');
        }

        firstListener = this._listeners[type];

        if (!firstListener) {
            return;
        }

        // we make sure we fire instances of our home made
        // events here. We wrap them only once, though
        if (data instanceof InternalEvent) {
            // we are fine, we alread have an event
            event = data;
        } else {
            event = this.createEvent(data);
        }

        // ensure we pass the event as the first parameter
        args[0] = event;

        // original event type (in case we delegate)
        var originalType = event.type;

        // update event type before delegation
        if (type !== originalType) {
            event.type = type;
        }

        try {
            returnValue = this._invokeListeners(event, args, firstListener);
        } finally {
            // reset event type after delegation
            if (type !== originalType) {
                event.type = originalType;
            }
        }

        // set the return value to false if the event default
        // got prevented and no other return value exists
        if (returnValue === undefined && event.defaultPrevented) {
            returnValue = false;
        }

        return returnValue;
    };


    EventBus.prototype.handleError = function(error) {
        return this.fire('error', { error: error }) === false;
    };


    EventBus.prototype._destroy = function() {
        this._listeners = {};
    };

    EventBus.prototype._invokeListeners = function(event, args, listener) {

        var returnValue;

        while (listener) {

            // handle stopped propagation
            if (event.cancelBubble) {
                break;
            }

            returnValue = this._invokeListener(event, args, listener);

            listener = listener.next;
        }

        return returnValue;
    };

    EventBus.prototype._invokeListener = function(event, args, listener) {

        var returnValue;

        try {
            // returning false prevents the default action
            returnValue = invokeFunction(listener.callback, args);

            // stop propagation on return value
            if (returnValue !== undefined) {
                event.returnValue = returnValue;
                event.stopPropagation();
            }

            // prevent default on return false
            if (returnValue === false) {
                event.preventDefault();
            }
        } catch (e) {
            if (!this.handleError(e)) {
                console.error('unhandled error in event listener');
                console.error(e.stack);

                throw e;
            }
        }

        return returnValue;
    };

    /*
	 * Add new listener with a certain priority to the list
	 * of listeners (for the given event).
	 *
	 * The semantics of listener registration / listener execution are
	 * first register, first serve: New listeners will always be inserted
	 * after existing listeners with the same priority.
	 *
	 * Example: Inserting two listeners with priority 1000 and 1300
	 *
	 *    * before: [ 1500, 1500, 1000, 1000 ]
	 *    * after: [ 1500, 1500, (new=1300), 1000, 1000, (new=1000) ]
	 *
	 * @param {String} event
	 * @param {Object} listener { priority, callback }
	 */
    EventBus.prototype._addListener = function(event, newListener) {

        var listener = this._getListeners(event),
            previousListener;

        // no prior listeners
        if (!listener) {
            this._setListeners(event, newListener);

            return;
        }

        // ensure we order listeners by priority from
        // 0 (high) to n > 0 (low)
        while (listener) {

            if (listener.priority < newListener.priority) {

                newListener.next = listener;

                if (previousListener) {
                    previousListener.next = newListener;
                } else {
                    this._setListeners(event, newListener);
                }

                return;
            }

            previousListener = listener;
            listener = listener.next;
        }

        // add new listener to back
        previousListener.next = newListener;
    };


    EventBus.prototype._getListeners = function(name) {
        return this._listeners[name];
    };

    EventBus.prototype._setListeners = function(name, listener) {
        this._listeners[name] = listener;
    };

    EventBus.prototype._removeListener = function(event, callback) {

        var listener = this._getListeners(event),
            nextListener,
            previousListener,
            listenerCallback;

        if (!callback) {
            // clear listeners
            this._setListeners(event, null);

            return;
        }

        while (listener) {

            nextListener = listener.next;

            listenerCallback = listener.callback;

            if (listenerCallback === callback || listenerCallback[FN_REF] === callback) {
                if (previousListener) {
                    previousListener.next = nextListener;
                } else {
                    // new first listener
                    this._setListeners(event, nextListener);
                }
            }

            previousListener = listener;
            listener = nextListener;
        }
    };

    /**
     * A event that is emitted via the event bus.
     */
    function InternalEvent() { }

    InternalEvent.prototype.stopPropagation = function() {
        this.cancelBubble = true;
    };

    InternalEvent.prototype.preventDefault = function() {
        this.defaultPrevented = true;
    };

    InternalEvent.prototype.init = function(data) {
        assign(this, data || {});
    };


    /**
     * Invoke function. Be fast...
     *
     * @param {Function} fn
     * @param {Array<Object>} args
     *
     * @return {Any}
     */
    function invokeFunction(fn, args) {
        return fn.apply(null, args);
    }

    /**
     * SVGs for elements are generated by the {@link GraphicsFactory}.
     *
     * This utility gives quick access to the important semantic
     * parts of an element.
     */

    /**
     * Returns the visual part of a diagram element
     *
     * @param {Snap<SVGElement>} gfx
     *
     * @return {Snap<SVGElement>}
     */
    function getVisual(gfx) {
        return query('.djs-visual', gfx);
    }

    /**
     * Returns the children for a given diagram element.
     *
     * @param {Snap<SVGElement>} gfx
     * @return {Snap<SVGElement>}
     */
    function getChildren(gfx) {
        return gfx.parentNode.childNodes[1];
    }

    /**
     * @param {<SVGElement>} element
     * @param {Number} x
     * @param {Number} y
     * @param {Number} angle
     * @param {Number} amount
     */
    function transform$1(gfx, x, y, angle, amount) {
        var translate = createTransform();
        translate.setTranslate(x, y);

        var rotate = createTransform();
        rotate.setRotate(angle, 0, 0);

        var scale = createTransform();
        scale.setScale(amount || 1, amount || 1);

        transform(gfx, [ translate, rotate, scale ]);
    }


    /**
     * @param {SVGElement} element
     * @param {Number} x
     * @param {Number} y
     */
    function translate(gfx, x, y) {
        var translate = createTransform();
        translate.setTranslate(x, y);

        transform(gfx, translate);
    }


    /**
     * @param {SVGElement} element
     * @param {Number} angle
     */
    function rotate(gfx, angle) {
        var rotate = createTransform();
        rotate.setRotate(angle, 0, 0);

        transform(gfx, rotate);
    }

    /**
     * A factory that creates graphical elements
     *
     * @param {EventBus} eventBus
     * @param {ElementRegistry} elementRegistry
     */
    function GraphicsFactory(eventBus, elementRegistry) {
        this._eventBus = eventBus;
        this._elementRegistry = elementRegistry;
    }

    GraphicsFactory.$inject = [ 'eventBus' , 'elementRegistry' ];


    GraphicsFactory.prototype._getChildren = function(element) {

        var gfx = this._elementRegistry.getGraphics(element);

        var childrenGfx;

        // root element
        if (!element.parent) {
            childrenGfx = gfx;
        } else {
            childrenGfx = getChildren(gfx);
            if (!childrenGfx) {
                childrenGfx = create('g');
                classes$1(childrenGfx).add('djs-children');

                append(gfx.parentNode, childrenGfx);
            }
        }

        return childrenGfx;
    };

    /**
     * Clears the graphical representation of the element and returns the
     * cleared visual (the <g class="djs-visual" /> element).
     */
    GraphicsFactory.prototype._clear = function(gfx) {
        var visual = getVisual(gfx);

        clear(visual);

        return visual;
    };

    /**
     * Creates a gfx container for shapes and connections
     *
     * The layout is as follows:
     *
     * <g class="djs-group">
     *
     *   <!-- the gfx -->
     *   <g class="djs-element djs-(shape|connection|frame)">
     *     <g class="djs-visual">
     *       <!-- the renderer draws in here -->
     *     </g>
     *
     *     <!-- extensions (overlays, click box, ...) goes here
     *   </g>
     *
     *   <!-- the gfx child nodes -->
     *   <g class="djs-children"></g>
     * </g>
     *
     * @param {String} type the type of the element, i.e. shape | connection
     * @param {SVGElement} [childrenGfx]
     * @param {Number} [parentIndex] position to create container in parent
     * @param {Boolean} [isFrame] is frame element
     *
     * @return {SVGElement}
     */
    GraphicsFactory.prototype._createContainer = function(
        type, childrenGfx, parentIndex, isFrame
    ) {
        var outerGfx = create('g');
        classes$1(outerGfx).add('djs-group');

        // insert node at position
        if (typeof parentIndex !== 'undefined') {
            prependTo(outerGfx, childrenGfx, childrenGfx.childNodes[parentIndex]);
        } else {
            append(childrenGfx, outerGfx);
        }

        var gfx = create('g');
        classes$1(gfx).add('djs-element');
        classes$1(gfx).add('djs-' + type);

        if (isFrame) {
            classes$1(gfx).add('djs-frame');
        }

        append(outerGfx, gfx);

        // create visual
        var visual = create('g');
        classes$1(visual).add('djs-visual');

        append(gfx, visual);

        return gfx;
    };

    GraphicsFactory.prototype.create = function(type, element, parentIndex) {
        var childrenGfx = this._getChildren(element.parent);
        return this._createContainer(type, childrenGfx, parentIndex, isFrameElement(element));
    };

    GraphicsFactory.prototype.updateContainments = function(elements) {

        var self = this,
            elementRegistry = this._elementRegistry,
            parents;

        parents = reduce(elements, function(map, e) {

            if (e.parent) {
                map[e.parent.id] = e.parent;
            }

            return map;
        }, {});

        // update all parents of changed and reorganized their children
        // in the correct order (as indicated in our model)
        forEach(parents, function(parent) {

            var children = parent.children;

            if (!children) {
                return;
            }

            var childGfx = self._getChildren(parent);

            forEach(children.slice().reverse(), function(c) {
                var gfx = elementRegistry.getGraphics(c);

                prependTo(gfx.parentNode, childGfx);
            });
        });
    };

    GraphicsFactory.prototype.drawShape = function(visual, element) {
        var eventBus = this._eventBus;

        return eventBus.fire('render.shape', { gfx: visual, element: element });
    };

    GraphicsFactory.prototype.getShapePath = function(element) {
        var eventBus = this._eventBus;

        return eventBus.fire('render.getShapePath', element);
    };

    GraphicsFactory.prototype.drawConnection = function(visual, element) {
        var eventBus = this._eventBus;

        return eventBus.fire('render.connection', { gfx: visual, element: element });
    };

    GraphicsFactory.prototype.getConnectionPath = function(waypoints) {
        var eventBus = this._eventBus;

        return eventBus.fire('render.getConnectionPath', waypoints);
    };

    GraphicsFactory.prototype.update = function(type, element, gfx) {

        // do NOT update root element
        if (!element.parent) {
            return;
        }

        var visual = this._clear(gfx);

        // redraw
        if (type === 'shape') {
            this.drawShape(visual, element);

            // update positioning
            translate(gfx, element.x, element.y);
        } else
        if (type === 'connection') {
            this.drawConnection(visual, element);
        } else {
            throw new Error('unknown type: ' + type);
        }

        if (element.hidden) {
            attr$1(gfx, 'display', 'none');
        } else {
            attr$1(gfx, 'display', 'block');
        }
    };

    GraphicsFactory.prototype.remove = function(element) {
        var gfx = this._elementRegistry.getGraphics(element);

        // remove
        remove$1(gfx.parentNode);
    };


    // helpers //////////

    function prependTo(newNode, parentNode, siblingNode) {
        var node = siblingNode || parentNode.firstChild;

        // do not prepend node to itself to prevent IE from crashing
        // https://github.com/bpmn-io/bpmn-js/issues/746
        if (newNode === node) {
            return;
        }

        parentNode.insertBefore(newNode, node);
    }

    var CoreModule = {
        __depends__: [ DrawModule ],
        __init__: [ 'canvas' ],
        canvas: [ 'type', Canvas ],
        elementRegistry: [ 'type', ElementRegistry ],
        elementFactory: [ 'type', ElementFactory ],
        eventBus: [ 'type', EventBus ],
        graphicsFactory: [ 'type', GraphicsFactory ]
    };

    /**
     * Bootstrap an injector from a list of modules, instantiating a number of default components
     *
     * @ignore
     * @param {Array<didi.Module>} bootstrapModules
     *
     * @return {didi.Injector} a injector to use to access the components
     */
    function bootstrap(bootstrapModules) {

        var modules = [],
            components = [];

        function hasModule(m) {
            return modules.indexOf(m) >= 0;
        }

        function addModule(m) {
            modules.push(m);
        }

        function visit(m) {
            if (hasModule(m)) {
                return;
            }

            (m.__depends__ || []).forEach(visit);

            if (hasModule(m)) {
                return;
            }

            addModule(m);

            (m.__init__ || []).forEach(function(c) {
                components.push(c);
            });
        }

        bootstrapModules.forEach(visit);

        var injector = new Injector(modules);

        components.forEach(function(c) {

            try {
                // eagerly resolve component (fn or string)
                injector[typeof c === 'string' ? 'get' : 'invoke'](c);
            } catch (e) {
                console.error('Failed to instantiate component');
                console.error(e.stack);

                throw e;
            }
        });

        return injector;
    }

    /**
     * Creates an injector from passed options.
     *
     * @ignore
     * @param  {Object} options
     * @return {didi.Injector}
     */
    function createInjector(options) {

        options = options || {};

        var configModule = {
            'config': ['value', options]
        };

        var modules = [ configModule, CoreModule ].concat(options.modules || []);

        return bootstrap(modules);
    }


    /**
     * The main diagram-js entry point that bootstraps the diagram with the given
     * configuration.
     *
     * To register extensions with the diagram, pass them as Array<didi.Module> to the constructor.
     *
     * @class djs.Diagram
     * @memberOf djs
     * @constructor
     *
     * @example
     *
     * <caption>Creating a plug-in that logs whenever a shape is added to the canvas.</caption>
     *
     * // plug-in implemenentation
     * function MyLoggingPlugin(eventBus) {
     *   eventBus.on('shape.added', function(event) {
     *     console.log('shape ', event.shape, ' was added to the diagram');
     *   });
     * }
     *
     * // export as module
     * export default {
     *   __init__: [ 'myLoggingPlugin' ],
     *     myLoggingPlugin: [ 'type', MyLoggingPlugin ]
     * };
     *
     *
     * // instantiate the diagram with the new plug-in
     *
     * import MyLoggingModule from 'path-to-my-logging-plugin';
     *
     * var diagram = new Diagram({
     *   modules: [
     *     MyLoggingModule
     *   ]
     * });
     *
     * diagram.invoke([ 'canvas', function(canvas) {
     *   // add shape to drawing canvas
     *   canvas.addShape({ x: 10, y: 10 });
     * });
     *
     * // 'shape ... was added to the diagram' logged to console
     *
     * @param {Object} options
     * @param {Array<didi.Module>} [options.modules] external modules to instantiate with the diagram
     * @param {didi.Injector} [injector] an (optional) injector to bootstrap the diagram with
     */
    function Diagram(options, injector) {

        // create injector unless explicitly specified
        this.injector = injector = injector || createInjector(options);

        // API

        /**
         * Resolves a diagram service
         *
         * @method Diagram#get
         *
         * @param {String} name the name of the diagram service to be retrieved
         * @param {Boolean} [strict=true] if false, resolve missing services to null
         */
        this.get = injector.get;

        /**
         * Executes a function into which diagram services are injected
         *
         * @method Diagram#invoke
         *
         * @param {Function|Object[]} fn the function to resolve
         * @param {Object} locals a number of locals to use to resolve certain dependencies
         */
        this.invoke = injector.invoke;

        // init

        // indicate via event


        /**
         * An event indicating that all plug-ins are loaded.
         *
         * Use this event to fire other events to interested plug-ins
         *
         * @memberOf Diagram
         *
         * @event diagram.init
         *
         * @example
         *
         * eventBus.on('diagram.init', function() {
         *   eventBus.fire('my-custom-event', { foo: 'BAR' });
         * });
         *
         * @type {Object}
         */
        this.get('eventBus').fire('diagram.init');
    }


    /**
     * Destroys the diagram
     *
     * @method  Diagram#destroy
     */
    Diagram.prototype.destroy = function() {
        this.get('eventBus').fire('diagram.destroy');
    };

    /**
     * Clear the diagram, removing all contents.
     */
    Diagram.prototype.clear = function() {
        this.get('eventBus').fire('diagram.clear');
    };

    /**
     * Moddle base element.
     */
    function Base$1() { }

    Base$1.prototype.get = function(name) {
        return this.$model.properties.get(this, name);
    };

    Base$1.prototype.set = function(name, value) {
        this.$model.properties.set(this, name, value);
    };

    /**
     * A model element factory.
     *
     * @param {Moddle} model
     * @param {Properties} properties
     */
    function Factory(model, properties) {
        this.model = model;
        this.properties = properties;
    }


    Factory.prototype.createType = function(descriptor) {

        var model = this.model;

        var props = this.properties,
            prototype = Object.create(Base$1.prototype);

        // initialize default values
        forEach(descriptor.properties, function(p) {
            if (!p.isMany && p.default !== undefined) {
                prototype[p.name] = p.default;
            }
        });

        props.defineModel(prototype, model);
        props.defineDescriptor(prototype, descriptor);

        var name = descriptor.ns.name;

        /**
         * The new type constructor
         */
        function ModdleElement(attrs) {
            props.define(this, '$type', { value: name, enumerable: true });
            props.define(this, '$attrs', { value: {} });
            props.define(this, '$parent', { writable: true });

            forEach(attrs, bind(function(val, key) {
                this.set(key, val);
            }, this));
        }

        ModdleElement.prototype = prototype;

        ModdleElement.hasType = prototype.$instanceOf = this.model.hasType;

        // static links
        props.defineModel(ModdleElement, model);
        props.defineDescriptor(ModdleElement, descriptor);

        return ModdleElement;
    };

    /**
     * Built-in moddle types
     */
    var BUILTINS = {
        String: true,
        Boolean: true,
        Integer: true,
        Real: true,
        Element: true
    };

    /**
     * Converters for built in types from string representations
     */
    var TYPE_CONVERTERS = {
        String: function(s) { return s; },
        Boolean: function(s) { return s === 'true'; },
        Integer: function(s) { return parseInt(s, 10); },
        Real: function(s) { return parseFloat(s, 10); }
    };

    /**
     * Convert a type to its real representation
     */
    function coerceType(type, value) {

        var converter = TYPE_CONVERTERS[type];

        if (converter) {
            return converter(value);
        } else {
            return value;
        }
    }

    /**
     * Return whether the given type is built-in
     */
    function isBuiltIn(type) {
        return !!BUILTINS[type];
    }

    /**
     * Return whether the given type is simple
     */
    function isSimple(type) {
        return !!TYPE_CONVERTERS[type];
    }

    /**
     * Parses a namespaced attribute name of the form (ns:)localName to an object,
     * given a default prefix to assume in case no explicit namespace is given.
     *
     * @param {String} name
     * @param {String} [defaultPrefix] the default prefix to take, if none is present.
     *
     * @return {Object} the parsed name
     */
    function parseName(name, defaultPrefix) {
        var parts = name.split(/:/),
            localName, prefix;

        // no prefix (i.e. only local name)
        if (parts.length === 1) {
            localName = name;
            prefix = defaultPrefix;
        } else
        // prefix + local name
        if (parts.length === 2) {
            localName = parts[1];
            prefix = parts[0];
        } else {
            throw new Error('expected <prefix:localName> or <localName>, got ' + name);
        }

        name = (prefix ? prefix + ':' : '') + localName;

        return {
            name: name,
            prefix: prefix,
            localName: localName
        };
    }

    /**
     * A utility to build element descriptors.
     */
    function DescriptorBuilder(nameNs) {
        this.ns = nameNs;
        this.name = nameNs.name;
        this.allTypes = [];
        this.allTypesByName = {};
        this.properties = [];
        this.propertiesByName = {};
    }


    DescriptorBuilder.prototype.build = function() {
        return pick(this, [
            'ns',
            'name',
            'allTypes',
            'allTypesByName',
            'properties',
            'propertiesByName',
            'bodyProperty',
            'idProperty'
        ]);
    };

    /**
     * Add property at given index.
     *
     * @param {Object} p
     * @param {Number} [idx]
     * @param {Boolean} [validate=true]
     */
    DescriptorBuilder.prototype.addProperty = function(p, idx, validate) {

        if (typeof idx === 'boolean') {
            validate = idx;
            idx = undefined;
        }

        this.addNamedProperty(p, validate !== false);

        var properties = this.properties;

        if (idx !== undefined) {
            properties.splice(idx, 0, p);
        } else {
            properties.push(p);
        }
    };


    DescriptorBuilder.prototype.replaceProperty = function(oldProperty, newProperty, replace) {
        var oldNameNs = oldProperty.ns;

        var props = this.properties,
            propertiesByName = this.propertiesByName,
            rename = oldProperty.name !== newProperty.name;

        if (oldProperty.isId) {
            if (!newProperty.isId) {
                throw new Error(
                    'property <' + newProperty.ns.name + '> must be id property ' +
                    'to refine <' + oldProperty.ns.name + '>');
            }

            this.setIdProperty(newProperty, false);
        }

        if (oldProperty.isBody) {

            if (!newProperty.isBody) {
                throw new Error(
                    'property <' + newProperty.ns.name + '> must be body property ' +
                    'to refine <' + oldProperty.ns.name + '>');
            }

            // TODO: Check compatibility
            this.setBodyProperty(newProperty, false);
        }

        // validate existence and get location of old property
        var idx = props.indexOf(oldProperty);
        if (idx === -1) {
            throw new Error('property <' + oldNameNs.name + '> not found in property list');
        }

        // remove old property
        props.splice(idx, 1);

        // replacing the named property is intentional
        //
        //  * validate only if this is a "rename" operation
        //  * add at specific index unless we "replace"
        //
        this.addProperty(newProperty, replace ? undefined : idx, rename);

        // make new property available under old name
        propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty;
    };


    DescriptorBuilder.prototype.redefineProperty = function(p, targetPropertyName, replace) {

        var nsPrefix = p.ns.prefix;
        var parts = targetPropertyName.split('#');

        var name = parseName(parts[0], nsPrefix);
        var attrName = parseName(parts[1], name.prefix).name;

        var redefinedProperty = this.propertiesByName[attrName];
        if (!redefinedProperty) {
            throw new Error('refined property <' + attrName + '> not found');
        } else {
            this.replaceProperty(redefinedProperty, p, replace);
        }

        delete p.redefines;
    };

    DescriptorBuilder.prototype.addNamedProperty = function(p, validate) {
        var ns = p.ns,
            propsByName = this.propertiesByName;

        if (validate) {
            this.assertNotDefined(p, ns.name);
            this.assertNotDefined(p, ns.localName);
        }

        propsByName[ns.name] = propsByName[ns.localName] = p;
    };

    DescriptorBuilder.prototype.removeNamedProperty = function(p) {
        var ns = p.ns,
            propsByName = this.propertiesByName;

        delete propsByName[ns.name];
        delete propsByName[ns.localName];
    };

    DescriptorBuilder.prototype.setBodyProperty = function(p, validate) {

        if (validate && this.bodyProperty) {
            throw new Error(
                'body property defined multiple times ' +
                '(<' + this.bodyProperty.ns.name + '>, <' + p.ns.name + '>)');
        }

        this.bodyProperty = p;
    };

    DescriptorBuilder.prototype.setIdProperty = function(p, validate) {

        if (validate && this.idProperty) {
            throw new Error(
                'id property defined multiple times ' +
                '(<' + this.idProperty.ns.name + '>, <' + p.ns.name + '>)');
        }

        this.idProperty = p;
    };

    DescriptorBuilder.prototype.assertNotDefined = function(p, name) {
        var propertyName = p.name,
            definedProperty = this.propertiesByName[propertyName];

        if (definedProperty) {
            throw new Error(
                'property <' + propertyName + '> already defined; ' +
                'override of <' + definedProperty.definedBy.ns.name + '#' + definedProperty.ns.name + '> by ' +
                '<' + p.definedBy.ns.name + '#' + p.ns.name + '> not allowed without redefines');
        }
    };

    DescriptorBuilder.prototype.hasProperty = function(name) {
        return this.propertiesByName[name];
    };

    DescriptorBuilder.prototype.addTrait = function(t, inherited) {

        var typesByName = this.allTypesByName,
            types = this.allTypes;

        var typeName = t.name;

        if (typeName in typesByName) {
            return;
        }

        forEach(t.properties, bind(function(p) {

            // clone property to allow extensions
            p = assign({}, p, {
                name: p.ns.localName,
                inherited: inherited
            });

            Object.defineProperty(p, 'definedBy', {
                value: t
            });

            var replaces = p.replaces,
                redefines = p.redefines;

            // add replace/redefine support
            if (replaces || redefines) {
                this.redefineProperty(p, replaces || redefines, replaces);
            } else {
                if (p.isBody) {
                    this.setBodyProperty(p);
                }
                if (p.isId) {
                    this.setIdProperty(p);
                }
                this.addProperty(p);
            }
        }, this));

        types.push(t);
        typesByName[typeName] = t;
    };

    /**
     * A registry of Moddle packages.
     *
     * @param {Array<Package>} packages
     * @param {Properties} properties
     */
    function Registry(packages, properties) {
        this.packageMap = {};
        this.typeMap = {};

        this.packages = [];

        this.properties = properties;

        forEach(packages, bind(this.registerPackage, this));
    }


    Registry.prototype.getPackage = function(uriOrPrefix) {
        return this.packageMap[uriOrPrefix];
    };

    Registry.prototype.getPackages = function() {
        return this.packages;
    };


    Registry.prototype.registerPackage = function(pkg) {

        // copy package
        pkg = assign({}, pkg);

        var pkgMap = this.packageMap;

        ensureAvailable(pkgMap, pkg, 'prefix');
        ensureAvailable(pkgMap, pkg, 'uri');

        // register types
        forEach(pkg.types, bind(function(descriptor) {
            this.registerType(descriptor, pkg);
        }, this));

        pkgMap[pkg.uri] = pkgMap[pkg.prefix] = pkg;
        this.packages.push(pkg);
    };


    /**
     * Register a type from a specific package with us
     */
    Registry.prototype.registerType = function(type, pkg) {

        type = assign({}, type, {
            superClass: (type.superClass || []).slice(),
            extends: (type.extends || []).slice(),
            properties: (type.properties || []).slice(),
            meta: assign((type.meta || {}))
        });

        var ns = parseName(type.name, pkg.prefix),
            name = ns.name,
            propertiesByName = {};

        // parse properties
        forEach(type.properties, bind(function(p) {

            // namespace property names
            var propertyNs = parseName(p.name, ns.prefix),
                propertyName = propertyNs.name;

            // namespace property types
            if (!isBuiltIn(p.type)) {
                p.type = parseName(p.type, propertyNs.prefix).name;
            }

            assign(p, {
                ns: propertyNs,
                name: propertyName
            });

            propertiesByName[propertyName] = p;
        }, this));

        // update ns + name
        assign(type, {
            ns: ns,
            name: name,
            propertiesByName: propertiesByName
        });

        forEach(type.extends, bind(function(extendsName) {
            var extended = this.typeMap[extendsName];

            extended.traits = extended.traits || [];
            extended.traits.push(name);
        }, this));

        // link to package
        this.definePackage(type, pkg);

        // register
        this.typeMap[name] = type;
    };


    /**
     * Traverse the type hierarchy from bottom to top,
     * calling iterator with (type, inherited) for all elements in
     * the inheritance chain.
     *
     * @param {Object} nsName
     * @param {Function} iterator
     * @param {Boolean} [trait=false]
     */
    Registry.prototype.mapTypes = function(nsName, iterator, trait) {

        var type = isBuiltIn(nsName.name) ? { name: nsName.name } : this.typeMap[nsName.name];

        var self = this;

        /**
         * Traverse the selected trait.
         *
         * @param {String} cls
         */
        function traverseTrait(cls) {
            return traverseSuper(cls, true);
        }

        /**
         * Traverse the selected super type or trait
         *
         * @param {String} cls
         * @param {Boolean} [trait=false]
         */
        function traverseSuper(cls, trait) {
            var parentNs = parseName(cls, isBuiltIn(cls) ? '' : nsName.prefix);
            self.mapTypes(parentNs, iterator, trait);
        }

        if (!type) {
            throw new Error('unknown type <' + nsName.name + '>');
        }

        forEach(type.superClass, trait ? traverseTrait : traverseSuper);

        // call iterator with (type, inherited=!trait)
        iterator(type, !trait);

        forEach(type.traits, traverseTrait);
    };


    /**
     * Returns the effective descriptor for a type.
     *
     * @param  {String} type the namespaced name (ns:localName) of the type
     *
     * @return {Descriptor} the resulting effective descriptor
     */
    Registry.prototype.getEffectiveDescriptor = function(name) {

        var nsName = parseName(name);

        var builder = new DescriptorBuilder(nsName);

        this.mapTypes(nsName, function(type, inherited) {
            builder.addTrait(type, inherited);
        });

        var descriptor = builder.build();

        // define package link
        this.definePackage(descriptor, descriptor.allTypes[descriptor.allTypes.length - 1].$pkg);

        return descriptor;
    };


    Registry.prototype.definePackage = function(target, pkg) {
        this.properties.define(target, '$pkg', { value: pkg });
    };



    ///////// helpers ////////////////////////////

    function ensureAvailable(packageMap, pkg, identifierKey) {

        var value = pkg[identifierKey];

        if (value in packageMap) {
            throw new Error('package with ' + identifierKey + ' <' + value + '> already defined');
        }
    }

    /**
     * A utility that gets and sets properties of model elements.
     *
     * @param {Model} model
     */
    function Properties(model) {
        this.model = model;
    }


    /**
     * Sets a named property on the target element.
     * If the value is undefined, the property gets deleted.
     *
     * @param {Object} target
     * @param {String} name
     * @param {Object} value
     */
    Properties.prototype.set = function(target, name, value) {

        var property = this.model.getPropertyDescriptor(target, name);

        var propertyName = property && property.name;

        if (isUndefined$1(value)) {
            // unset the property, if the specified value is undefined;
            // delete from $attrs (for extensions) or the target itself
            if (property) {
                delete target[propertyName];
            } else {
                delete target.$attrs[name];
            }
        } else {
            // set the property, defining well defined properties on the fly
            // or simply updating them in target.$attrs (for extensions)
            if (property) {
                if (propertyName in target) {
                    target[propertyName] = value;
                } else {
                    defineProperty$1(target, property, value);
                }
            } else {
                target.$attrs[name] = value;
            }
        }
    };

    /**
     * Returns the named property of the given element
     *
     * @param  {Object} target
     * @param  {String} name
     *
     * @return {Object}
     */
    Properties.prototype.get = function(target, name) {

        var property = this.model.getPropertyDescriptor(target, name);

        if (!property) {
            return target.$attrs[name];
        }

        var propertyName = property.name;

        // check if access to collection property and lazily initialize it
        if (!target[propertyName] && property.isMany) {
            defineProperty$1(target, property, []);
        }

        return target[propertyName];
    };


    /**
     * Define a property on the target element
     *
     * @param  {Object} target
     * @param  {String} name
     * @param  {Object} options
     */
    Properties.prototype.define = function(target, name, options) {
        Object.defineProperty(target, name, options);
    };


    /**
     * Define the descriptor for an element
     */
    Properties.prototype.defineDescriptor = function(target, descriptor) {
        this.define(target, '$descriptor', { value: descriptor });
    };

    /**
     * Define the model for an element
     */
    Properties.prototype.defineModel = function(target, model) {
        this.define(target, '$model', { value: model });
    };


    function isUndefined$1(val) {
        return typeof val === 'undefined';
    }

    function defineProperty$1(target, property, value) {
        Object.defineProperty(target, property.name, {
            enumerable: !property.isReference,
            writable: true,
            value: value,
            configurable: true
        });
    }

    //// Moddle implementation /////////////////////////////////////////////////

    /**
     * @class Moddle
     *
     * A model that can be used to create elements of a specific type.
     *
     * @example
     *
     * var Moddle = require('moddle');
     *
     * var pkg = {
     *   name: 'mypackage',
     *   prefix: 'my',
     *   types: [
     *     { name: 'Root' }
     *   ]
     * };
     *
     * var moddle = new Moddle([pkg]);
     *
     * @param {Array<Package>} packages the packages to contain
     */
    function Moddle(packages) {

        this.properties = new Properties(this);

        this.factory = new Factory(this, this.properties);
        this.registry = new Registry(packages, this.properties);

        this.typeCache = {};
    }


    /**
     * Create an instance of the specified type.
     *
     * @method Moddle#create
     *
     * @example
     *
     * var foo = moddle.create('my:Foo');
     * var bar = moddle.create('my:Bar', { id: 'BAR_1' });
     *
     * @param  {String|Object} descriptor the type descriptor or name know to the model
     * @param  {Object} attrs   a number of attributes to initialize the model instance with
     * @return {Object}         model instance
     */
    Moddle.prototype.create = function(descriptor, attrs) {
        var Type = this.getType(descriptor);

        if (!Type) {
            throw new Error('unknown type <' + descriptor + '>');
        }

        return new Type(attrs);
    };


    /**
     * Returns the type representing a given descriptor
     *
     * @method Moddle#getType
     *
     * @example
     *
     * var Foo = moddle.getType('my:Foo');
     * var foo = new Foo({ 'id' : 'FOO_1' });
     *
     * @param  {String|Object} descriptor the type descriptor or name know to the model
     * @return {Object}         the type representing the descriptor
     */
    Moddle.prototype.getType = function(descriptor) {

        var cache = this.typeCache;

        var name = isString(descriptor) ? descriptor : descriptor.ns.name;

        var type = cache[name];

        if (!type) {
            descriptor = this.registry.getEffectiveDescriptor(name);
            type = cache[name] = this.factory.createType(descriptor);
        }

        return type;
    };


    /**
     * Creates an any-element type to be used within model instances.
     *
     * This can be used to create custom elements that lie outside the meta-model.
     * The created element contains all the meta-data required to serialize it
     * as part of meta-model elements.
     *
     * @method Moddle#createAny
     *
     * @example
     *
     * var foo = moddle.createAny('vendor:Foo', 'http://vendor', {
     *   value: 'bar'
     * });
     *
     * var container = moddle.create('my:Container', 'http://my', {
     *   any: [ foo ]
     * });
     *
     * // go ahead and serialize the stuff
     *
     *
     * @param  {String} name  the name of the element
     * @param  {String} nsUri the namespace uri of the element
     * @param  {Object} [properties] a map of properties to initialize the instance with
     * @return {Object} the any type instance
     */
    Moddle.prototype.createAny = function(name, nsUri, properties) {

        var nameNs = parseName(name);

        var element = {
            $type: name,
            $instanceOf: function(type) {
                return type === this.$type;
            }
        };

        var descriptor = {
            name: name,
            isGeneric: true,
            ns: {
                prefix: nameNs.prefix,
                localName: nameNs.localName,
                uri: nsUri
            }
        };

        this.properties.defineDescriptor(element, descriptor);
        this.properties.defineModel(element, this);
        this.properties.define(element, '$parent', { enumerable: false, writable: true });

        forEach(properties, function(a, key) {
            if (isObject(a) && a.value !== undefined) {
                element[a.name] = a.value;
            } else {
                element[key] = a;
            }
        });

        return element;
    };

    /**
     * Returns a registered package by uri or prefix
     *
     * @return {Object} the package
     */
    Moddle.prototype.getPackage = function(uriOrPrefix) {
        return this.registry.getPackage(uriOrPrefix);
    };

    /**
     * Returns a snapshot of all known packages
     *
     * @return {Object} the package
     */
    Moddle.prototype.getPackages = function() {
        return this.registry.getPackages();
    };

    /**
     * Returns the descriptor for an element
     */
    Moddle.prototype.getElementDescriptor = function(element) {
        return element.$descriptor;
    };

    /**
     * Returns true if the given descriptor or instance
     * represents the given type.
     *
     * May be applied to this, if element is omitted.
     */
    Moddle.prototype.hasType = function(element, type) {
        if (type === undefined) {
            type = element;
            element = this;
        }

        var descriptor = element.$model.getElementDescriptor(element);

        return (type in descriptor.allTypesByName);
    };

    /**
     * Returns the descriptor of an elements named property
     */
    Moddle.prototype.getPropertyDescriptor = function(element, property) {
        return this.getElementDescriptor(element).propertiesByName[property];
    };

    /**
     * Returns a mapped type's descriptor
     */
    Moddle.prototype.getTypeDescriptor = function(type) {
        return this.registry.typeMap[type];
    };

    var fromCharCode = String.fromCharCode;

    var hasOwnProperty$1 = Object.prototype.hasOwnProperty;

    var ENTITY_PATTERN = /&#(\d+);|&#x([0-9a-f]+);|&(\w+);/ig;

    var ENTITY_MAPPING = {
        'amp': '&',
        'apos': '\'',
        'gt': '>',
        'lt': '<',
        'quot': '"'
    };

    // map UPPERCASE variants of supported special chars
    Object.keys(ENTITY_MAPPING).forEach(function(k) {
        ENTITY_MAPPING[k.toUpperCase()] = ENTITY_MAPPING[k];
    });


    function replaceEntities(_, d, x, z) {

        // reserved names, i.e. &nbsp;
        if (z) {
            if (hasOwnProperty$1.call(ENTITY_MAPPING, z)) {
                return ENTITY_MAPPING[z];
            } else {
                // fall back to original value
                return '&' + z + ';';
            }
        }

        // decimal encoded char
        if (d) {
            return fromCharCode(d);
        }

        // hex encoded char
        return fromCharCode(parseInt(x, 16));
    }


    /**
     * A basic entity decoder that can decode a minimal
     * sub-set of reserved names (&amp;) as well as
     * hex (&#xaaf;) and decimal (&#1231;) encoded characters.
     *
     * @param {string} str
     *
     * @return {string} decoded string
     */
    function decodeEntities(s) {
        if (s.length > 3 && s.indexOf('&') !== -1) {
            return s.replace(ENTITY_PATTERN, replaceEntities);
        }

        return s;
    }

    var XSI_URI = 'http://www.w3.org/2001/XMLSchema-instance';
    var XSI_PREFIX = 'xsi';
    var XSI_TYPE = 'xsi:type';

    var NON_WHITESPACE_OUTSIDE_ROOT_NODE = 'non-whitespace outside of root node';

    function error(msg) {
        return new Error(msg);
    }

    function missingNamespaceForPrefix(prefix) {
        return 'missing namespace for prefix <' + prefix + '>';
    }

    function getter(getFn) {
        return {
            'get': getFn,
            'enumerable': true
        };
    }

    function cloneNsMatrix(nsMatrix) {
        var clone = {}, key;
        for (key in nsMatrix) {
            clone[key] = nsMatrix[key];
        }
        return clone;
    }

    function uriPrefix(prefix) {
        return prefix + '$uri';
    }

    function buildNsMatrix(nsUriToPrefix) {
        var nsMatrix = {},
            uri,
            prefix;

        for (uri in nsUriToPrefix) {
            prefix = nsUriToPrefix[uri];
            nsMatrix[prefix] = prefix;
            nsMatrix[uriPrefix(prefix)] = uri;
        }

        return nsMatrix;
    }

    function noopGetContext() {
        return { 'line': 0, 'column': 0 };
    }

    function throwFunc(err) {
        throw err;
    }

    /**
     * Creates a new parser with the given options.
     *
     * @constructor
     *
     * @param  {!Object<string, ?>=} options
     */
    function Parser(options) {

        if (!this) {
            return new Parser(options);
        }

        var proxy = options && options['proxy'];

        var onText,
            onOpenTag,
            onCloseTag,
            onCDATA,
            onError = throwFunc,
            onWarning,
            onComment,
            onQuestion,
            onAttention;

        var getContext = noopGetContext;

        /**
         * Do we need to parse the current elements attributes for namespaces?
         *
         * @type {boolean}
         */
        var maybeNS = false;

        /**
         * Do we process namespaces at all?
         *
         * @type {boolean}
         */
        var isNamespace = false;

        /**
         * The caught error returned on parse end
         *
         * @type {Error}
         */
        var returnError = null;

        /**
         * Should we stop parsing?
         *
         * @type {boolean}
         */
        var parseStop = false;

        /**
         * A map of { uri: prefix } used by the parser.
         *
         * This map will ensure we can normalize prefixes during processing;
         * for each uri, only one prefix will be exposed to the handlers.
         *
         * @type {!Object<string, string>}}
         */
        var nsUriToPrefix;

        /**
         * Handle parse error.
         *
         * @param  {string|Error} err
         */
        function handleError(err) {
            if (!(err instanceof Error)) {
                err = error(err);
            }

            returnError = err;

            onError(err, getContext);
        }

        /**
         * Handle parse error.
         *
         * @param  {string|Error} err
         */
        function handleWarning(err) {

            if (!onWarning) {
                return;
            }

            if (!(err instanceof Error)) {
                err = error(err);
            }

            onWarning(err, getContext);
        }

        /**
         * Register parse listener.
         *
         * @param  {string}   name
         * @param  {Function} cb
         *
         * @return {Parser}
         */
        this['on'] = function(name, cb) {

            if (typeof cb !== 'function') {
                throw error('required args <name, cb>');
            }

            switch (name) {
                case 'openTag': onOpenTag = cb; break;
                case 'text': onText = cb; break;
                case 'closeTag': onCloseTag = cb; break;
                case 'error': onError = cb; break;
                case 'warn': onWarning = cb; break;
                case 'cdata': onCDATA = cb; break;
                case 'attention': onAttention = cb; break; // <!XXXXX zzzz="eeee">
                case 'question': onQuestion = cb; break; // <? ....  ?>
                case 'comment': onComment = cb; break;
                default:
                    throw error('unsupported event: ' + name);
            }

            return this;
        };

        /**
         * Set the namespace to prefix mapping.
         *
         * @example
         *
         * parser.ns({
         *   'http://foo': 'foo',
         *   'http://bar': 'bar'
         * });
         *
         * @param  {!Object<string, string>} nsMap
         *
         * @return {Parser}
         */
        this['ns'] = function(nsMap) {

            if (typeof nsMap === 'undefined') {
                nsMap = {};
            }

            if (typeof nsMap !== 'object') {
                throw error('required args <nsMap={}>');
            }

            var _nsUriToPrefix = {}, k;

            for (k in nsMap) {
                _nsUriToPrefix[k] = nsMap[k];
            }

            // FORCE default mapping for schema instance
            _nsUriToPrefix[XSI_URI] = XSI_PREFIX;

            isNamespace = true;
            nsUriToPrefix = _nsUriToPrefix;

            return this;
        };

        /**
         * Parse xml string.
         *
         * @param  {string} xml
         *
         * @return {Error} returnError, if not thrown
         */
        this['parse'] = function(xml) {
            if (typeof xml !== 'string') {
                throw error('required args <xml=string>');
            }

            returnError = null;

            parse(xml);

            getContext = noopGetContext;
            parseStop = false;

            return returnError;
        };

        /**
         * Stop parsing.
         */
        this['stop'] = function() {
            parseStop = true;
        };

        /**
         * Parse string, invoking configured listeners on element.
         *
         * @param  {string} xml
         */
        function parse(xml) {
            var nsMatrixStack = isNamespace ? [] : null,
                nsMatrix = isNamespace ? buildNsMatrix(nsUriToPrefix) : null,
                _nsMatrix,
                nodeStack = [],
                anonymousNsCount = 0,
                tagStart = false,
                tagEnd = false,
                i = 0, j = 0,
                x, y, q, w,
                xmlns,
                elementName,
                _elementName,
                elementProxy
            ;

            var attrsString = '',
                attrsStart = 0,
                cachedAttrs // false = parsed with errors, null = needs parsing
            ;

            /**
             * Parse attributes on demand and returns the parsed attributes.
             *
             * Return semantics: (1) `false` on attribute parse error,
             * (2) object hash on extracted attrs.
             *
             * @return {boolean|Object}
             */
            function getAttrs() {
                if (cachedAttrs !== null) {
                    return cachedAttrs;
                }

                var nsUri,
                    nsUriPrefix,
                    nsName,
                    defaultAlias = isNamespace && nsMatrix['xmlns'],
                    attrList = isNamespace && maybeNS ? [] : null,
                    i = attrsStart,
                    s = attrsString,
                    l = s.length,
                    hasNewMatrix,
                    newalias,
                    value,
                    alias,
                    name,
                    attrs = {},
                    seenAttrs = {},
                    skipAttr,
                    w,
                    j;

                parseAttr:
                    for (; i < l; i++) {
                        skipAttr = false;
                        w = s.charCodeAt(i);

                        if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE={ \f\n\r\t\v}
                            continue;
                        }

                        // wait for non whitespace character
                        if (w < 65 || w > 122 || (w > 90 && w < 97)) {
                            if (w !== 95 && w !== 58) { // char 95"_" 58":"
                                handleWarning('illegal first char attribute name');
                                skipAttr = true;
                            }
                        }

                        // parse attribute name
                        for (j = i + 1; j < l; j++) {
                            w = s.charCodeAt(j);

                            if (
                                w > 96 && w < 123 ||
                                w > 64 && w < 91 ||
                                w > 47 && w < 59 ||
                                w === 46 || // '.'
                                w === 45 || // '-'
                                w === 95 // '_'
                            ) {
                                continue;
                            }

                            // unexpected whitespace
                            if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE
                                handleWarning('missing attribute value');
                                i = j;

                                continue parseAttr;
                            }

                            // expected "="
                            if (w === 61) { // "=" == 61
                                break;
                            }

                            handleWarning('illegal attribute name char');
                            skipAttr = true;
                        }

                        name = s.substring(i, j);

                        if (name === 'xmlns:xmlns') {
                            handleWarning('illegal declaration of xmlns');
                            skipAttr = true;
                        }

                        w = s.charCodeAt(j + 1);

                        if (w === 34) { // '"'
                            j = s.indexOf('"', i = j + 2);

                            if (j === -1) {
                                j = s.indexOf('\'', i);

                                if (j !== -1) {
                                    handleWarning('attribute value quote missmatch');
                                    skipAttr = true;
                                }
                            }

                        } else if (w === 39) { // "'"
                            j = s.indexOf('\'', i = j + 2);

                            if (j === -1) {
                                j = s.indexOf('"', i);

                                if (j !== -1) {
                                    handleWarning('attribute value quote missmatch');
                                    skipAttr = true;
                                }
                            }

                        } else {
                            handleWarning('missing attribute value quotes');
                            skipAttr = true;

                            // skip to next space
                            for (j = j + 1; j < l; j++) {
                                w = s.charCodeAt(j + 1);

                                if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE
                                    break;
                                }
                            }

                        }

                        if (j === -1) {
                            handleWarning('missing closing quotes');

                            j = l;
                            skipAttr = true;
                        }

                        if (!skipAttr) {
                            value = s.substring(i, j);
                        }

                        i = j;

                        // ensure SPACE follows attribute
                        // skip illegal content otherwise
                        // example a="b"c
                        for (; j + 1 < l; j++) {
                            w = s.charCodeAt(j + 1);

                            if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE
                                break;
                            }

                            // FIRST ILLEGAL CHAR
                            if (i === j) {
                                handleWarning('illegal character after attribute end');
                                skipAttr = true;
                            }
                        }

                        // advance cursor to next attribute
                        i = j + 1;

                        if (skipAttr) {
                            continue parseAttr;
                        }

                        // check attribute re-declaration
                        if (name in seenAttrs) {
                            handleWarning('attribute <' + name + '> already defined');
                            continue;
                        }

                        seenAttrs[name] = true;

                        if (!isNamespace) {
                            attrs[name] = value;
                            continue;
                        }

                        // try to extract namespace information
                        if (maybeNS) {
                            newalias = (
                                name === 'xmlns'
                                    ? 'xmlns'
                                    : (name.charCodeAt(0) === 120 && name.substr(0, 6) === 'xmlns:')
                                    ? name.substr(6)
                                    : null
                            );

                            // handle xmlns(:alias) assignment
                            if (newalias !== null) {
                                nsUri = decodeEntities(value);
                                nsUriPrefix = uriPrefix(newalias);

                                alias = nsUriToPrefix[nsUri];

                                if (!alias) {
                                    // no prefix defined or prefix collision
                                    if (
                                        (newalias === 'xmlns') ||
                                        (nsUriPrefix in nsMatrix && nsMatrix[nsUriPrefix] !== nsUri)
                                    ) {
                                        // alocate free ns prefix
                                        do {
                                            alias = 'ns' + (anonymousNsCount++);
                                        } while (typeof nsMatrix[alias] !== 'undefined');
                                    } else {
                                        alias = newalias;
                                    }

                                    nsUriToPrefix[nsUri] = alias;
                                }

                                if (nsMatrix[newalias] !== alias) {
                                    if (!hasNewMatrix) {
                                        nsMatrix = cloneNsMatrix(nsMatrix);
                                        hasNewMatrix = true;
                                    }

                                    nsMatrix[newalias] = alias;
                                    if (newalias === 'xmlns') {
                                        nsMatrix[uriPrefix(alias)] = nsUri;
                                        defaultAlias = alias;
                                    }

                                    nsMatrix[nsUriPrefix] = nsUri;
                                }

                                // expose xmlns(:asd)="..." in attributes
                                attrs[name] = value;
                                continue;
                            }

                            // collect attributes until all namespace
                            // declarations are processed
                            attrList.push(name, value);
                            continue;

                        } /** end if (maybeNs) */

                        // handle attributes on element without
                        // namespace declarations
                        w = name.indexOf(':');
                        if (w === -1) {
                            attrs[name] = value;
                            continue;
                        }

                        // normalize ns attribute name
                        if (!(nsName = nsMatrix[name.substring(0, w)])) {
                            handleWarning(missingNamespaceForPrefix(name.substring(0, w)));
                            continue;
                        }

                        name = defaultAlias === nsName
                            ? name.substr(w + 1)
                            : nsName + name.substr(w);
                        // end: normalize ns attribute name

                        // normalize xsi:type ns attribute value
                        if (name === XSI_TYPE) {
                            w = value.indexOf(':');

                            if (w !== -1) {
                                nsName = value.substring(0, w);
                                // handle default prefixes, i.e. xs:String gracefully
                                nsName = nsMatrix[nsName] || nsName;
                                value = nsName + value.substring(w);
                            } else {
                                value = defaultAlias + ':' + value;
                            }
                        }
                        // end: normalize xsi:type ns attribute value

                        attrs[name] = value;
                    }


                // handle deferred, possibly namespaced attributes
                if (maybeNS) {

                    // normalize captured attributes
                    for (i = 0, l = attrList.length; i < l; i++) {

                        name = attrList[i++];
                        value = attrList[i];

                        w = name.indexOf(':');

                        if (w !== -1) {
                            // normalize ns attribute name
                            if (!(nsName = nsMatrix[name.substring(0, w)])) {
                                handleWarning(missingNamespaceForPrefix(name.substring(0, w)));
                                continue;
                            }

                            name = defaultAlias === nsName
                                ? name.substr(w + 1)
                                : nsName + name.substr(w);
                            // end: normalize ns attribute name

                            // normalize xsi:type ns attribute value
                            if (name === XSI_TYPE) {
                                w = value.indexOf(':');

                                if (w !== -1) {
                                    nsName = value.substring(0, w);
                                    // handle default prefixes, i.e. xs:String gracefully
                                    nsName = nsMatrix[nsName] || nsName;
                                    value = nsName + value.substring(w);
                                } else {
                                    value = defaultAlias + ':' + value;
                                }
                            }
                            // end: normalize xsi:type ns attribute value
                        }

                        attrs[name] = value;
                    }
                    // end: normalize captured attributes
                }

                return cachedAttrs = attrs;
            }

            /**
             * Extract the parse context { line, column, part }
             * from the current parser position.
             *
             * @return {Object} parse context
             */
            function getParseContext() {
                var splitsRe = /(\r\n|\r|\n)/g;

                var line = 0;
                var column = 0;
                var startOfLine = 0;
                var endOfLine = j;
                var match;
                var data;

                while (i >= startOfLine) {

                    match = splitsRe.exec(xml);

                    if (!match) {
                        break;
                    }

                    // end of line = (break idx + break chars)
                    endOfLine = match[0].length + match.index;

                    if (endOfLine > i) {
                        break;
                    }

                    // advance to next line
                    line += 1;

                    startOfLine = endOfLine;
                }

                // EOF errors
                if (i == -1) {
                    column = endOfLine;
                    data = xml.substring(j);
                } else
                // start errors
                if (j === 0) {
                    console.log(i - startOfLine);
                    data = xml.substring(j, i);
                }
                // other errors
                else {
                    column = i - startOfLine;
                    data = (j == -1 ? xml.substring(i) : xml.substring(i, j + 1));
                }

                return {
                    'data': data,
                    'line': line,
                    'column': column
                };
            }

            getContext = getParseContext;


            if (proxy) {
                elementProxy = Object.create({}, {
                    'name': getter(function() {
                        return elementName;
                    }),
                    'originalName': getter(function() {
                        return _elementName;
                    }),
                    'attrs': getter(getAttrs),
                    'ns': getter(function() {
                        return nsMatrix;
                    })
                });
            }

            // actual parse logic
            while (j !== -1) {

                if (xml.charCodeAt(j) === 60) { // "<"
                    i = j;
                } else {
                    i = xml.indexOf('<', j);
                }

                // parse end
                if (i === -1) {
                    if (nodeStack.length) {
                        return handleError('unexpected end of file');
                    }

                    if (j === 0) {
                        return handleError('missing start tag');
                    }

                    if (j < xml.length) {
                        if (xml.substring(j).trim()) {
                            handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE);
                        }
                    }

                    return;
                }

                // parse text
                if (j !== i) {

                    if (nodeStack.length) {
                        if (onText) {
                            onText(xml.substring(j, i), decodeEntities, getContext);

                            if (parseStop) {
                                return;
                            }
                        }
                    } else {
                        if (xml.substring(j, i).trim()) {
                            handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE);

                            if (parseStop) {
                                return;
                            }
                        }
                    }
                }

                w = xml.charCodeAt(i+1);

                // parse comments + CDATA
                if (w === 33) { // "!"
                    w = xml.charCodeAt(i+2);
                    if (w === 91 && xml.substr(i + 3, 6) === 'CDATA[') { // 91 == "["
                        j = xml.indexOf(']]>', i);
                        if (j === -1) {
                            return handleError('unclosed cdata');
                        }

                        if (onCDATA) {
                            onCDATA(xml.substring(i + 9, j), getContext);
                            if (parseStop) {
                                return;
                            }
                        }

                        j += 3;
                        continue;
                    }


                    if (w === 45 && xml.charCodeAt(i + 3) === 45) { // 45 == "-"
                        j = xml.indexOf('-->', i);
                        if (j === -1) {
                            return handleError('unclosed comment');
                        }


                        if (onComment) {
                            onComment(xml.substring(i + 4, j), decodeEntities, getContext);
                            if (parseStop) {
                                return;
                            }
                        }

                        j += 3;
                        continue;
                    }

                    j = xml.indexOf('>', i + 1);
                    if (j === -1) {
                        return handleError('unclosed tag');
                    }

                    if (onAttention) {
                        onAttention(xml.substring(i, j + 1), decodeEntities, getContext);
                        if (parseStop) {
                            return;
                        }
                    }

                    j += 1;
                    continue;
                }

                if (w === 63) { // "?"
                    j = xml.indexOf('?>', i);
                    if (j === -1) {
                        return handleError('unclosed question');
                    }

                    if (onQuestion) {
                        onQuestion(xml.substring(i, j + 2), getContext);
                        if (parseStop) {
                            return;
                        }
                    }

                    j += 2;
                    continue;
                }

                j = xml.indexOf('>', i + 1);

                if (j == -1) {
                    return handleError('unclosed tag');
                }

                // don't process attributes;
                // there are none
                cachedAttrs = {};

                // if (xml.charCodeAt(i+1) === 47) { // </...
                if (w === 47) { // </...
                    tagStart = false;
                    tagEnd = true;

                    if (!nodeStack.length) {
                        return handleError('missing open tag');
                    }

                    // verify open <-> close tag match
                    x = elementName = nodeStack.pop();
                    q = i + 2 + x.length;

                    if (xml.substring(i + 2, q) !== x) {
                        return handleError('closing tag mismatch');
                    }

                    // verify chars in close tag
                    for (; q < j; q++) {
                        w = xml.charCodeAt(q);

                        if (w === 32 || (w > 8 && w < 14)) { // \f\n\r\t\v space
                            continue;
                        }

                        return handleError('close tag');
                    }

                } else {
                    if (xml.charCodeAt(j - 1) === 47) { // .../>
                        x = elementName = xml.substring(i + 1, j - 1);

                        tagStart = true;
                        tagEnd = true;

                    } else {
                        x = elementName = xml.substring(i + 1, j);

                        tagStart = true;
                        tagEnd = false;
                    }

                    if (!(w > 96 && w < 123 || w > 64 && w < 91 || w === 95 || w === 58)) { // char 95"_" 58":"
                        return handleError('illegal first char nodeName');
                    }

                    for (q = 1, y = x.length; q < y; q++) {
                        w = x.charCodeAt(q);

                        if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w == 46) {
                            continue;
                        }

                        if (w === 32 || (w < 14 && w > 8)) { // \f\n\r\t\v space
                            elementName = x.substring(0, q);
                            // maybe there are attributes
                            cachedAttrs = null;
                            break;
                        }

                        return handleError('invalid nodeName');
                    }

                    if (!tagEnd) {
                        nodeStack.push(elementName);
                    }
                }

                if (isNamespace) {

                    _nsMatrix = nsMatrix;

                    if (tagStart) {
                        // remember old namespace
                        // unless we're self-closing
                        if (!tagEnd) {
                            nsMatrixStack.push(_nsMatrix);
                        }

                        if (cachedAttrs === null) {
                            // quick check, whether there may be namespace
                            // declarations on the node; if that is the case
                            // we need to eagerly parse the node attributes
                            if ((maybeNS = x.indexOf('xmlns', q) !== -1)) {
                                attrsStart = q;
                                attrsString = x;

                                getAttrs();

                                maybeNS = false;
                            }
                        }
                    }

                    _elementName = elementName;

                    w = elementName.indexOf(':');
                    if (w !== -1) {
                        xmlns = nsMatrix[elementName.substring(0, w)];

                        // prefix given; namespace must exist
                        if (!xmlns) {
                            return handleError('missing namespace on <' + _elementName + '>');
                        }

                        elementName = elementName.substr(w + 1);
                    } else {
                        xmlns = nsMatrix['xmlns'];

                        // if no default namespace is defined,
                        // we'll import the element as anonymous.
                        //
                        // it is up to users to correct that to the document defined
                        // targetNamespace, or whatever their undersanding of the
                        // XML spec mandates.
                    }

                    // adjust namespace prefixs as configured
                    if (xmlns) {
                        elementName = xmlns + ':' + elementName;
                    }

                }

                if (tagStart) {
                    attrsStart = q;
                    attrsString = x;

                    if (onOpenTag) {
                        if (proxy) {
                            onOpenTag(elementProxy, decodeEntities, tagEnd, getContext);
                        } else {
                            onOpenTag(elementName, getAttrs, decodeEntities, tagEnd, getContext);
                        }

                        if (parseStop) {
                            return;
                        }
                    }

                }

                if (tagEnd) {

                    if (onCloseTag) {
                        onCloseTag(proxy ? elementProxy : elementName, decodeEntities, tagStart, getContext);

                        if (parseStop) {
                            return;
                        }
                    }

                    // restore old namespace
                    if (isNamespace) {
                        if (!tagStart) {
                            nsMatrix = nsMatrixStack.pop();
                        } else {
                            nsMatrix = _nsMatrix;
                        }
                    }
                }

                j += 1;
            }
        } /** end parse */

    }

    function hasLowerCaseAlias(pkg) {
        return pkg.xml && pkg.xml.tagAlias === 'lowerCase';
    }

    var DEFAULT_NS_MAP = {
        'xsi': 'http://www.w3.org/2001/XMLSchema-instance'
    };

    var XSI_TYPE$1 = 'xsi:type';

    function serializeFormat(element) {
        return element.xml && element.xml.serialize;
    }

    function serializeAsType(element) {
        return serializeFormat(element) === XSI_TYPE$1;
    }

    function serializeAsProperty(element) {
        return serializeFormat(element) === 'property';
    }

    function capitalize(str) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    }

    function aliasToName(aliasNs, pkg) {

        if (!hasLowerCaseAlias(pkg)) {
            return aliasNs.name;
        }

        return aliasNs.prefix + ':' + capitalize(aliasNs.localName);
    }

    function prefixedToName(nameNs, pkg) {

        var name = nameNs.name,
            localName = nameNs.localName;

        var typePrefix = pkg.xml && pkg.xml.typePrefix;

        if (typePrefix && localName.indexOf(typePrefix) === 0) {
            return nameNs.prefix + ':' + localName.slice(typePrefix.length);
        } else {
            return name;
        }
    }

    function normalizeXsiTypeName(name, model) {

        var nameNs = parseName(name);
        var pkg = model.getPackage(nameNs.prefix);

        return prefixedToName(nameNs, pkg);
    }

    function error$1(message) {
        return new Error(message);
    }

    /**
     * Get the moddle descriptor for a given instance or type.
     *
     * @param  {ModdleElement|Function} element
     *
     * @return {Object} the moddle descriptor
     */
    function getModdleDescriptor(element) {
        return element.$descriptor;
    }

    function defer(fn) {
        setTimeout(fn, 0);
    }

    /**
     * A parse context.
     *
     * @class
     *
     * @param {Object} options
     * @param {ElementHandler} options.rootHandler the root handler for parsing a document
     * @param {boolean} [options.lax=false] whether or not to ignore invalid elements
     */
    function Context(options) {

        /**
         * @property {ElementHandler} rootHandler
         */

        /**
         * @property {Boolean} lax
         */

        assign(this, options);

        this.elementsById = {};
        this.references = [];
        this.warnings = [];

        /**
         * Add an unresolved reference.
         *
         * @param {Object} reference
         */
        this.addReference = function(reference) {
            this.references.push(reference);
        };

        /**
         * Add a processed element.
         *
         * @param {ModdleElement} element
         */
        this.addElement = function(element) {

            if (!element) {
                throw error$1('expected element');
            }

            var elementsById = this.elementsById;

            var descriptor = getModdleDescriptor(element);

            var idProperty = descriptor.idProperty,
                id;

            if (idProperty) {
                id = element.get(idProperty.name);

                if (id) {
                    // for QName validation as per http://www.w3.org/TR/REC-xml/#NT-NameChar
                    if (!/^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i.test(id)) {
                        throw new Error('illegal ID <' + id + '>');
                    }

                    if (elementsById[id]) {
                        throw error$1('duplicate ID <' + id + '>');
                    }

                    elementsById[id] = element;
                }
            }
        };

        /**
         * Add an import warning.
         *
         * @param {Object} warning
         * @param {String} warning.message
         * @param {Error} [warning.error]
         */
        this.addWarning = function(warning) {
            this.warnings.push(warning);
        };
    }

    function BaseHandler() {}

    BaseHandler.prototype.handleEnd = function() {};
    BaseHandler.prototype.handleText = function() {};
    BaseHandler.prototype.handleNode = function() {};


    /**
     * A simple pass through handler that does nothing except for
     * ignoring all input it receives.
     *
     * This is used to ignore unknown elements and
     * attributes.
     */
    function NoopHandler() { }

    NoopHandler.prototype = Object.create(BaseHandler.prototype);

    NoopHandler.prototype.handleNode = function() {
        return this;
    };

    function BodyHandler() {}

    BodyHandler.prototype = Object.create(BaseHandler.prototype);

    BodyHandler.prototype.handleText = function(text) {
        this.body = (this.body || '') + text;
    };

    function ReferenceHandler(property, context) {
        this.property = property;
        this.context = context;
    }

    ReferenceHandler.prototype = Object.create(BodyHandler.prototype);

    ReferenceHandler.prototype.handleNode = function(node) {

        if (this.element) {
            throw error$1('expected no sub nodes');
        } else {
            this.element = this.createReference(node);
        }

        return this;
    };

    ReferenceHandler.prototype.handleEnd = function() {
        this.element.id = this.body;
    };

    ReferenceHandler.prototype.createReference = function(node) {
        return {
            property: this.property.ns.name,
            id: ''
        };
    };

    function ValueHandler(propertyDesc, element) {
        this.element = element;
        this.propertyDesc = propertyDesc;
    }

    ValueHandler.prototype = Object.create(BodyHandler.prototype);

    ValueHandler.prototype.handleEnd = function() {

        var value = this.body || '',
            element = this.element,
            propertyDesc = this.propertyDesc;

        value = coerceType(propertyDesc.type, value);

        if (propertyDesc.isMany) {
            element.get(propertyDesc.name).push(value);
        } else {
            element.set(propertyDesc.name, value);
        }
    };


    function BaseElementHandler() {}

    BaseElementHandler.prototype = Object.create(BodyHandler.prototype);

    BaseElementHandler.prototype.handleNode = function(node) {
        var parser = this,
            element = this.element;

        if (!element) {
            element = this.element = this.createElement(node);

            this.context.addElement(element);
        } else {
            parser = this.handleChild(node);
        }

        return parser;
    };

    /**
     * @class Reader.ElementHandler
     *
     */
    function ElementHandler(model, typeName, context) {
        this.model = model;
        this.type = model.getType(typeName);
        this.context = context;
    }

    ElementHandler.prototype = Object.create(BaseElementHandler.prototype);

    ElementHandler.prototype.addReference = function(reference) {
        this.context.addReference(reference);
    };

    ElementHandler.prototype.handleText = function(text) {

        var element = this.element,
            descriptor = getModdleDescriptor(element),
            bodyProperty = descriptor.bodyProperty;

        if (!bodyProperty) {
            throw error$1('unexpected body text <' + text + '>');
        }

        BodyHandler.prototype.handleText.call(this, text);
    };

    ElementHandler.prototype.handleEnd = function() {

        var value = this.body,
            element = this.element,
            descriptor = getModdleDescriptor(element),
            bodyProperty = descriptor.bodyProperty;

        if (bodyProperty && value !== undefined) {
            value = coerceType(bodyProperty.type, value);
            element.set(bodyProperty.name, value);
        }
    };

    /**
     * Create an instance of the model from the given node.
     *
     * @param  {Element} node the xml node
     */
    ElementHandler.prototype.createElement = function(node) {
        var attributes = node.attributes,
            Type = this.type,
            descriptor = getModdleDescriptor(Type),
            context = this.context,
            instance = new Type({}),
            model = this.model,
            propNameNs;

        forEach(attributes, function(value, name) {

            var prop = descriptor.propertiesByName[name],
                values;

            if (prop && prop.isReference) {

                if (!prop.isMany) {
                    context.addReference({
                        element: instance,
                        property: prop.ns.name,
                        id: value
                    });
                } else {
                    // IDREFS: parse references as whitespace-separated list
                    values = value.split(' ');

                    forEach(values, function(v) {
                        context.addReference({
                            element: instance,
                            property: prop.ns.name,
                            id: v
                        });
                    });
                }

            } else {
                if (prop) {
                    value = coerceType(prop.type, value);
                } else
                if (name !== 'xmlns') {
                    propNameNs = parseName(name, descriptor.ns.prefix);

                    // check whether attribute is defined in a well-known namespace
                    // if that is the case we emit a warning to indicate potential misuse
                    if (model.getPackage(propNameNs.prefix)) {

                        context.addWarning({
                            message: 'unknown attribute <' + name + '>',
                            element: instance,
                            property: name,
                            value: value
                        });
                    }
                }

                instance.set(name, value);
            }
        });

        return instance;
    };

    ElementHandler.prototype.getPropertyForNode = function(node) {

        var name = node.name;
        var nameNs = parseName(name);

        var type = this.type,
            model = this.model,
            descriptor = getModdleDescriptor(type);

        var propertyName = nameNs.name,
            property = descriptor.propertiesByName[propertyName],
            elementTypeName,
            elementType;

        // search for properties by name first

        if (property) {

            if (serializeAsType(property)) {
                elementTypeName = node.attributes[XSI_TYPE$1];

                // xsi type is optional, if it does not exists the
                // default type is assumed
                if (elementTypeName) {

                    // take possible type prefixes from XML
                    // into account, i.e.: xsi:type="t{ActualType}"
                    elementTypeName = normalizeXsiTypeName(elementTypeName, model);

                    elementType = model.getType(elementTypeName);

                    return assign({}, property, {
                        effectiveType: getModdleDescriptor(elementType).name
                    });
                }
            }

            // search for properties by name first
            return property;
        }

        var pkg = model.getPackage(nameNs.prefix);

        if (pkg) {
            elementTypeName = aliasToName(nameNs, pkg);
            elementType = model.getType(elementTypeName);

            // search for collection members later
            property = find(descriptor.properties, function(p) {
                return !p.isVirtual && !p.isReference && !p.isAttribute && elementType.hasType(p.type);
            });

            if (property) {
                return assign({}, property, {
                    effectiveType: getModdleDescriptor(elementType).name
                });
            }
        } else {
            // parse unknown element (maybe extension)
            property = find(descriptor.properties, function(p) {
                return !p.isReference && !p.isAttribute && p.type === 'Element';
            });

            if (property) {
                return property;
            }
        }

        throw error$1('unrecognized element <' + nameNs.name + '>');
    };

    ElementHandler.prototype.toString = function() {
        return 'ElementDescriptor[' + getModdleDescriptor(this.type).name + ']';
    };

    ElementHandler.prototype.valueHandler = function(propertyDesc, element) {
        return new ValueHandler(propertyDesc, element);
    };

    ElementHandler.prototype.referenceHandler = function(propertyDesc) {
        return new ReferenceHandler(propertyDesc, this.context);
    };

    ElementHandler.prototype.handler = function(type) {
        if (type === 'Element') {
            return new GenericElementHandler(this.model, type, this.context);
        } else {
            return new ElementHandler(this.model, type, this.context);
        }
    };

    /**
     * Handle the child element parsing
     *
     * @param  {Element} node the xml node
     */
    ElementHandler.prototype.handleChild = function(node) {
        var propertyDesc, type, element, childHandler;

        propertyDesc = this.getPropertyForNode(node);
        element = this.element;

        type = propertyDesc.effectiveType || propertyDesc.type;

        if (isSimple(type)) {
            return this.valueHandler(propertyDesc, element);
        }

        if (propertyDesc.isReference) {
            childHandler = this.referenceHandler(propertyDesc).handleNode(node);
        } else {
            childHandler = this.handler(type).handleNode(node);
        }

        var newElement = childHandler.element;

        // child handles may decide to skip elements
        // by not returning anything
        if (newElement !== undefined) {

            if (propertyDesc.isMany) {
                element.get(propertyDesc.name).push(newElement);
            } else {
                element.set(propertyDesc.name, newElement);
            }

            if (propertyDesc.isReference) {
                assign(newElement, {
                    element: element
                });

                this.context.addReference(newElement);
            } else {
                // establish child -> parent relationship
                newElement.$parent = element;
            }
        }

        return childHandler;
    };

    /**
     * An element handler that performs special validation
     * to ensure the node it gets initialized with matches
     * the handlers type (namespace wise).
     *
     * @param {Moddle} model
     * @param {String} typeName
     * @param {Context} context
     */
    function RootElementHandler(model, typeName, context) {
        ElementHandler.call(this, model, typeName, context);
    }

    RootElementHandler.prototype = Object.create(ElementHandler.prototype);

    RootElementHandler.prototype.createElement = function(node) {

        var name = node.name,
            nameNs = parseName(name),
            model = this.model,
            type = this.type,
            pkg = model.getPackage(nameNs.prefix),
            typeName = pkg && aliasToName(nameNs, pkg) || name;

        // verify the correct namespace if we parse
        // the first element in the handler tree
        //
        // this ensures we don't mistakenly import wrong namespace elements
        if (!type.hasType(typeName)) {
            throw error$1('unexpected element <' + node.originalName + '>');
        }

        return ElementHandler.prototype.createElement.call(this, node);
    };


    function GenericElementHandler(model, typeName, context) {
        this.model = model;
        this.context = context;
    }

    GenericElementHandler.prototype = Object.create(BaseElementHandler.prototype);

    GenericElementHandler.prototype.createElement = function(node) {

        var name = node.name,
            ns = parseName(name),
            prefix = ns.prefix,
            uri = node.ns[prefix + '$uri'],
            attributes = node.attributes;

        return this.model.createAny(name, uri, attributes);
    };

    GenericElementHandler.prototype.handleChild = function(node) {

        var handler = new GenericElementHandler(this.model, 'Element', this.context).handleNode(node),
            element = this.element;

        var newElement = handler.element,
            children;

        if (newElement !== undefined) {
            children = element.$children = element.$children || [];
            children.push(newElement);

            // establish child -> parent relationship
            newElement.$parent = element;
        }

        return handler;
    };

    GenericElementHandler.prototype.handleEnd = function() {
        if (this.body) {
            this.element.$body = this.body;
        }
    };

    /**
     * A reader for a meta-model
     *
     * @param {Object} options
     * @param {Model} options.model used to read xml files
     * @param {Boolean} options.lax whether to make parse errors warnings
     */
    function Reader(options) {

        if (options instanceof Moddle) {
            options = {
                model: options
            };
        }

        assign(this, { lax: false }, options);
    }


    /**
     * Parse the given XML into a moddle document tree.
     *
     * @param {String} xml
     * @param {ElementHandler|Object} options or rootHandler
     * @param  {Function} done
     */
    Reader.prototype.fromXML = function(xml, options, done) {

        var rootHandler = options.rootHandler;

        if (options instanceof ElementHandler) {
            // root handler passed via (xml, { rootHandler: ElementHandler }, ...)
            rootHandler = options;
            options = {};
        } else {
            if (typeof options === 'string') {
                // rootHandler passed via (xml, 'someString', ...)
                rootHandler = this.handler(options);
                options = {};
            } else if (typeof rootHandler === 'string') {
                // rootHandler passed via (xml, { rootHandler: 'someString' }, ...)
                rootHandler = this.handler(rootHandler);
            }
        }

        var model = this.model,
            lax = this.lax;

        var context = new Context(assign({}, options, { rootHandler: rootHandler })),
            parser = new Parser({ proxy: true }),
            stack = createStack();

        rootHandler.context = context;

        // push root handler
        stack.push(rootHandler);


        /**
         * Handle error.
         *
         * @param  {Error} err
         * @param  {Function} getContext
         * @param  {boolean} lax
         *
         * @return {boolean} true if handled
         */
        function handleError(err, getContext, lax) {

            var ctx = getContext();

            var line = ctx.line,
                column = ctx.column,
                data = ctx.data;

            // we receive the full context data here,
            // for elements trim down the information
            // to the tag name, only
            if (data.charAt(0) === '<' && data.indexOf(' ') !== -1) {
                data = data.slice(0, data.indexOf(' ')) + '>';
            }

            var message =
                'unparsable content ' + (data ? data + ' ' : '') + 'detected\n\t' +
                'line: ' + line + '\n\t' +
                'column: ' + column + '\n\t' +
                'nested error: ' + err.message;

            if (lax) {
                context.addWarning({
                    message: message,
                    error: err
                });

                return true;
            } else {
                throw error$1(message);
            }
        }

        function handleWarning(err, getContext) {
            // just like handling errors in <lax=true> mode
            return handleError(err, getContext, true);
        }

        /**
         * Resolve collected references on parse end.
         */
        function resolveReferences() {

            var elementsById = context.elementsById;
            var references = context.references;

            var i, r;

            for (i = 0; (r = references[i]); i++) {
                var element = r.element;
                var reference = elementsById[r.id];
                var property = getModdleDescriptor(element).propertiesByName[r.property];

                if (!reference) {
                    context.addWarning({
                        message: 'unresolved reference <' + r.id + '>',
                        element: r.element,
                        property: r.property,
                        value: r.id
                    });
                }

                if (property.isMany) {
                    var collection = element.get(property.name),
                        idx = collection.indexOf(r);

                    // we replace an existing place holder (idx != -1) or
                    // append to the collection instead
                    if (idx === -1) {
                        idx = collection.length;
                    }

                    if (!reference) {
                        // remove unresolvable reference
                        collection.splice(idx, 1);
                    } else {
                        // add or update reference in collection
                        collection[idx] = reference;
                    }
                } else {
                    element.set(property.name, reference);
                }
            }
        }

        function handleClose() {
            stack.pop().handleEnd();
        }

        var PREAMBLE_START_PATTERN = /^<\?xml /i;

        var ENCODING_PATTERN = / encoding="([^"]+)"/i;

        var UTF_8_PATTERN = /^utf-8$/i;

        function handleQuestion(question) {

            if (!PREAMBLE_START_PATTERN.test(question)) {
                return;
            }

            var match = ENCODING_PATTERN.exec(question);
            var encoding = match && match[1];

            if (!encoding || UTF_8_PATTERN.test(encoding)) {
                return;
            }

            context.addWarning({
                message:
                    'unsupported document encoding <' + encoding + '>, ' +
                    'falling back to UTF-8'
            });
        }

        function handleOpen(node, getContext) {
            var handler = stack.peek();

            try {
                stack.push(handler.handleNode(node));
            } catch (err) {

                if (handleError(err, getContext, lax)) {
                    stack.push(new NoopHandler());
                }
            }
        }

        function handleCData(text, getContext) {

            try {
                stack.peek().handleText(text);
            } catch (err) {
                handleWarning(err, getContext);
            }
        }

        function handleText(text, getContext) {
            // strip whitespace only nodes, i.e. before
            // <!CDATA[ ... ]> sections and in between tags
            text = text.trim();

            if (!text) {
                return;
            }

            handleCData(text, getContext);
        }

        var uriMap = model.getPackages().reduce(function(uriMap, p) {
            uriMap[p.uri] = p.prefix;

            return uriMap;
        }, {});

        parser
            .ns(uriMap)
            .on('openTag', function(obj, decodeStr, selfClosing, getContext) {

                // gracefully handle unparsable attributes (attrs=false)
                var attrs = obj.attrs || {};

                var decodedAttrs = Object.keys(attrs).reduce(function(d, key) {
                    var value = decodeStr(attrs[key]);

                    d[key] = value;

                    return d;
                }, {});

                var node = {
                    name: obj.name,
                    originalName: obj.originalName,
                    attributes: decodedAttrs,
                    ns: obj.ns
                };

                handleOpen(node, getContext);
            })
            .on('question', handleQuestion)
            .on('closeTag', handleClose)
            .on('cdata', handleCData)
            .on('text', function(text, decodeEntities, getContext) {
                handleText(decodeEntities(text), getContext);
            })
            .on('error', handleError)
            .on('warn', handleWarning);

        // deferred parse XML to make loading really ascnchronous
        // this ensures the execution environment (node or browser)
        // is kept responsive and that certain optimization strategies
        // can kick in
        defer(function() {
            var err;

            try {
                parser.parse(xml);

                resolveReferences();
            } catch (e) {
                err = e;
            }

            var element = rootHandler.element;

            // handle the situation that we could not extract
            // the desired root element from the document
            if (!err && !element) {
                err = error$1('failed to parse document as <' + rootHandler.type.$descriptor.name + '>');
            }

            done(err, err ? undefined : element, context);
        });
    };

    Reader.prototype.handler = function(name) {
        return new RootElementHandler(this.model, name);
    };


    // helpers //////////////////////////

    function createStack() {
        var stack = [];

        Object.defineProperty(stack, 'peek', {
            value: function() {
                return this[this.length - 1];
            }
        });

        return stack;
    }

    var XML_PREAMBLE = '<?xml version="1.0" encoding="UTF-8"?>\n';

    var ESCAPE_ATTR_CHARS = /<|>|'|"|&|\n\r|\n/g;
    var ESCAPE_CHARS =  /<|>|&/g;


    function Namespaces(parent) {

        var prefixMap = {};
        var uriMap = {};
        var used = {};

        var wellknown = [];
        var custom = [];

        // API

        this.byUri = function(uri) {
            return uriMap[uri] || (
                parent && parent.byUri(uri)
            );
        };

        this.add = function(ns, isWellknown) {

            uriMap[ns.uri] = ns;

            if (isWellknown) {
                wellknown.push(ns);
            } else {
                custom.push(ns);
            }

            this.mapPrefix(ns.prefix, ns.uri);
        };

        this.uriByPrefix = function(prefix) {
            return prefixMap[prefix || 'xmlns'];
        };

        this.mapPrefix = function(prefix, uri) {
            prefixMap[prefix || 'xmlns'] = uri;
        };

        this.logUsed = function(ns) {
            var uri = ns.uri;

            used[uri] = this.byUri(uri);
        };

        this.getUsed = function(ns) {

            function isUsed(ns) {
                return used[ns.uri];
            }

            var allNs = [].concat(wellknown, custom);

            return allNs.filter(isUsed);
        };

    }

    function lower(string) {
        return string.charAt(0).toLowerCase() + string.slice(1);
    }

    function nameToAlias(name, pkg) {
        if (hasLowerCaseAlias(pkg)) {
            return lower(name);
        } else {
            return name;
        }
    }

    function inherits(ctor, superCtor) {
        ctor.super_ = superCtor;
        ctor.prototype = Object.create(superCtor.prototype, {
            constructor: {
                value: ctor,
                enumerable: false,
                writable: true,
                configurable: true
            }
        });
    }

    function nsName(ns) {
        if (isString(ns)) {
            return ns;
        } else {
            return (ns.prefix ? ns.prefix + ':' : '') + ns.localName;
        }
    }

    function getNsAttrs(namespaces) {

        return map(namespaces.getUsed(), function(ns) {
            var name = 'xmlns' + (ns.prefix ? ':' + ns.prefix : '');
            return { name: name, value: ns.uri };
        });

    }

    function getElementNs(ns, descriptor) {
        if (descriptor.isGeneric) {
            return assign({ localName: descriptor.ns.localName }, ns);
        } else {
            return assign({ localName: nameToAlias(descriptor.ns.localName, descriptor.$pkg) }, ns);
        }
    }

    function getPropertyNs(ns, descriptor) {
        return assign({ localName: descriptor.ns.localName }, ns);
    }

    function getSerializableProperties(element) {
        var descriptor = element.$descriptor;

        return filter(descriptor.properties, function(p) {
            var name = p.name;

            if (p.isVirtual) {
                return false;
            }

            // do not serialize defaults
            if (!element.hasOwnProperty(name)) {
                return false;
            }

            var value = element[name];

            // do not serialize default equals
            if (value === p.default) {
                return false;
            }

            // do not serialize null properties
            if (value === null) {
                return false;
            }

            return p.isMany ? value.length : true;
        });
    }

    var ESCAPE_ATTR_MAP = {
        '\n': '#10',
        '\n\r': '#10',
        '"': '#34',
        '\'': '#39',
        '<': '#60',
        '>': '#62',
        '&': '#38'
    };

    var ESCAPE_MAP = {
        '<': 'lt',
        '>': 'gt',
        '&': 'amp'
    };

    function escape$1(str, charPattern, replaceMap) {

        // ensure we are handling strings here
        str = isString(str) ? str : '' + str;

        return str.replace(charPattern, function(s) {
            return '&' + replaceMap[s] + ';';
        });
    }

    /**
     * Escape a string attribute to not contain any bad values (line breaks, '"', ...)
     *
     * @param {String} str the string to escape
     * @return {String} the escaped string
     */
    function escapeAttr(str) {
        return escape$1(str, ESCAPE_ATTR_CHARS, ESCAPE_ATTR_MAP);
    }

    function escapeBody(str) {
        return escape$1(str, ESCAPE_CHARS, ESCAPE_MAP);
    }

    function filterAttributes(props) {
        return filter(props, function(p) { return p.isAttr; });
    }

    function filterContained(props) {
        return filter(props, function(p) { return !p.isAttr; });
    }


    function ReferenceSerializer(tagName) {
        this.tagName = tagName;
    }

    ReferenceSerializer.prototype.build = function(element) {
        this.element = element;
        return this;
    };

    ReferenceSerializer.prototype.serializeTo = function(writer) {
        writer
            .appendIndent()
            .append('<' + this.tagName + '>' + this.element.id + '</' + this.tagName + '>')
            .appendNewLine();
    };

    function BodySerializer() {}

    BodySerializer.prototype.serializeValue =
        BodySerializer.prototype.serializeTo = function(writer) {
            writer.append(
                this.escape
                    ? escapeBody(this.value)
                    : this.value
            );
        };

    BodySerializer.prototype.build = function(prop, value) {
        this.value = value;

        if (prop.type === 'String' && value.search(ESCAPE_CHARS) !== -1) {
            this.escape = true;
        }

        return this;
    };

    function ValueSerializer(tagName) {
        this.tagName = tagName;
    }

    inherits(ValueSerializer, BodySerializer);

    ValueSerializer.prototype.serializeTo = function(writer) {

        writer
            .appendIndent()
            .append('<' + this.tagName + '>');

        this.serializeValue(writer);

        writer
            .append('</' + this.tagName + '>')
            .appendNewLine();
    };

    function ElementSerializer(parent, propertyDescriptor) {
        this.body = [];
        this.attrs = [];

        this.parent = parent;
        this.propertyDescriptor = propertyDescriptor;
    }

    ElementSerializer.prototype.build = function(element) {
        this.element = element;

        var elementDescriptor = element.$descriptor,
            propertyDescriptor = this.propertyDescriptor;

        var otherAttrs,
            properties;

        var isGeneric = elementDescriptor.isGeneric;

        if (isGeneric) {
            otherAttrs = this.parseGeneric(element);
        } else {
            otherAttrs = this.parseNsAttributes(element);
        }

        if (propertyDescriptor) {
            this.ns = this.nsPropertyTagName(propertyDescriptor);
        } else {
            this.ns = this.nsTagName(elementDescriptor);
        }

        // compute tag name
        this.tagName = this.addTagName(this.ns);

        if (!isGeneric) {
            properties = getSerializableProperties(element);

            this.parseAttributes(filterAttributes(properties));
            this.parseContainments(filterContained(properties));
        }

        this.parseGenericAttributes(element, otherAttrs);

        return this;
    };

    ElementSerializer.prototype.nsTagName = function(descriptor) {
        var effectiveNs = this.logNamespaceUsed(descriptor.ns);
        return getElementNs(effectiveNs, descriptor);
    };

    ElementSerializer.prototype.nsPropertyTagName = function(descriptor) {
        var effectiveNs = this.logNamespaceUsed(descriptor.ns);
        return getPropertyNs(effectiveNs, descriptor);
    };

    ElementSerializer.prototype.isLocalNs = function(ns) {
        return ns.uri === this.ns.uri;
    };

    /**
     * Get the actual ns attribute name for the given element.
     *
     * @param {Object} element
     * @param {Boolean} [element.inherited=false]
     *
     * @return {Object} nsName
     */
    ElementSerializer.prototype.nsAttributeName = function(element) {

        var ns;

        if (isString(element)) {
            ns = parseName(element);
        } else {
            ns = element.ns;
        }

        // return just local name for inherited attributes
        if (element.inherited) {
            return { localName: ns.localName };
        }

        // parse + log effective ns
        var effectiveNs = this.logNamespaceUsed(ns);

        // LOG ACTUAL namespace use
        this.getNamespaces().logUsed(effectiveNs);

        // strip prefix if same namespace like parent
        if (this.isLocalNs(effectiveNs)) {
            return { localName: ns.localName };
        } else {
            return assign({ localName: ns.localName }, effectiveNs);
        }
    };

    ElementSerializer.prototype.parseGeneric = function(element) {

        var self = this,
            body = this.body;

        var attributes = [];

        forEach(element, function(val, key) {

            var nonNsAttr;

            if (key === '$body') {
                body.push(new BodySerializer().build({ type: 'String' }, val));
            } else
            if (key === '$children') {
                forEach(val, function(child) {
                    body.push(new ElementSerializer(self).build(child));
                });
            } else
            if (key.indexOf('$') !== 0) {
                nonNsAttr = self.parseNsAttribute(element, key, val);

                if (nonNsAttr) {
                    attributes.push({ name: key, value: val });
                }
            }
        });

        return attributes;
    };

    ElementSerializer.prototype.parseNsAttribute = function(element, name, value) {
        var model = element.$model;

        var nameNs = parseName(name);

        var ns;

        // parse xmlns:foo="http://foo.bar"
        if (nameNs.prefix === 'xmlns') {
            ns = { prefix: nameNs.localName, uri: value };
        }

        // parse xmlns="http://foo.bar"
        if (!nameNs.prefix && nameNs.localName === 'xmlns') {
            ns = { uri: value };
        }

        if (!ns) {
            return {
                name: name,
                value: value
            };
        }

        if (model && model.getPackage(value)) {
            // register well known namespace
            this.logNamespace(ns, true, true);
        } else {
            // log custom namespace directly as used
            var actualNs = this.logNamespaceUsed(ns, true);

            this.getNamespaces().logUsed(actualNs);
        }
    };


    /**
     * Parse namespaces and return a list of left over generic attributes
     *
     * @param  {Object} element
     * @return {Array<Object>}
     */
    ElementSerializer.prototype.parseNsAttributes = function(element, attrs) {
        var self = this;

        var genericAttrs = element.$attrs;

        var attributes = [];

        // parse namespace attributes first
        // and log them. push non namespace attributes to a list
        // and process them later
        forEach(genericAttrs, function(value, name) {

            var nonNsAttr = self.parseNsAttribute(element, name, value);

            if (nonNsAttr) {
                attributes.push(nonNsAttr);
            }
        });

        return attributes;
    };

    ElementSerializer.prototype.parseGenericAttributes = function(element, attributes) {

        var self = this;

        forEach(attributes, function(attr) {

            // do not serialize xsi:type attribute
            // it is set manually based on the actual implementation type
            if (attr.name === XSI_TYPE$1) {
                return;
            }

            try {
                self.addAttribute(self.nsAttributeName(attr.name), attr.value);
            } catch (e) {
                console.warn(
                    'missing namespace information for ',
                    attr.name, '=', attr.value, 'on', element,
                    e);
            }
        });
    };

    ElementSerializer.prototype.parseContainments = function(properties) {

        var self = this,
            body = this.body,
            element = this.element;

        forEach(properties, function(p) {
            var value = element.get(p.name),
                isReference = p.isReference,
                isMany = p.isMany;

            if (!isMany) {
                value = [ value ];
            }

            if (p.isBody) {
                body.push(new BodySerializer().build(p, value[0]));
            } else
            if (isSimple(p.type)) {
                forEach(value, function(v) {
                    body.push(new ValueSerializer(self.addTagName(self.nsPropertyTagName(p))).build(p, v));
                });
            } else
            if (isReference) {
                forEach(value, function(v) {
                    body.push(new ReferenceSerializer(self.addTagName(self.nsPropertyTagName(p))).build(v));
                });
            } else {
                // allow serialization via type
                // rather than element name
                var asType = serializeAsType(p),
                    asProperty = serializeAsProperty(p);

                forEach(value, function(v) {
                    var serializer;

                    if (asType) {
                        serializer = new TypeSerializer(self, p);
                    } else
                    if (asProperty) {
                        serializer = new ElementSerializer(self, p);
                    } else {
                        serializer = new ElementSerializer(self);
                    }

                    body.push(serializer.build(v));
                });
            }
        });
    };

    ElementSerializer.prototype.getNamespaces = function(local) {

        var namespaces = this.namespaces,
            parent = this.parent,
            parentNamespaces;

        if (!namespaces) {
            parentNamespaces = parent && parent.getNamespaces();

            if (local || !parentNamespaces) {
                this.namespaces = namespaces = new Namespaces(parentNamespaces);
            } else {
                namespaces = parentNamespaces;
            }
        }

        return namespaces;
    };

    ElementSerializer.prototype.logNamespace = function(ns, wellknown, local) {
        var namespaces = this.getNamespaces(local);

        var nsUri = ns.uri,
            nsPrefix = ns.prefix;

        var existing = namespaces.byUri(nsUri);

        if (!existing) {
            namespaces.add(ns, wellknown);
        }

        namespaces.mapPrefix(nsPrefix, nsUri);

        return ns;
    };

    ElementSerializer.prototype.logNamespaceUsed = function(ns, local) {
        var element = this.element,
            model = element.$model,
            namespaces = this.getNamespaces(local);

        // ns may be
        //
        //   * prefix only
        //   * prefix:uri
        //   * localName only

        var prefix = ns.prefix,
            uri = ns.uri,
            newPrefix, idx,
            wellknownUri;

        // handle anonymous namespaces (elementForm=unqualified), cf. #23
        if (!prefix && !uri) {
            return { localName: ns.localName };
        }

        wellknownUri = DEFAULT_NS_MAP[prefix] || model && (model.getPackage(prefix) || {}).uri;

        uri = uri || wellknownUri || namespaces.uriByPrefix(prefix);

        if (!uri) {
            throw new Error('no namespace uri given for prefix <' + prefix + '>');
        }

        ns = namespaces.byUri(uri);

        if (!ns) {
            newPrefix = prefix;
            idx = 1;

            // find a prefix that is not mapped yet
            while (namespaces.uriByPrefix(newPrefix)) {
                newPrefix = prefix + '_' + idx++;
            }

            ns = this.logNamespace({ prefix: newPrefix, uri: uri }, wellknownUri === uri);
        }

        if (prefix) {
            namespaces.mapPrefix(prefix, uri);
        }

        return ns;
    };

    ElementSerializer.prototype.parseAttributes = function(properties) {
        var self = this,
            element = this.element;

        forEach(properties, function(p) {

            var value = element.get(p.name);

            if (p.isReference) {

                if (!p.isMany) {
                    value = value.id;
                }
                else {
                    var values = [];
                    forEach(value, function(v) {
                        values.push(v.id);
                    });
                    // IDREFS is a whitespace-separated list of references.
                    value = values.join(' ');
                }

            }

            self.addAttribute(self.nsAttributeName(p), value);
        });
    };

    ElementSerializer.prototype.addTagName = function(nsTagName) {
        var actualNs = this.logNamespaceUsed(nsTagName);

        this.getNamespaces().logUsed(actualNs);

        return nsName(nsTagName);
    };

    ElementSerializer.prototype.addAttribute = function(name, value) {
        var attrs = this.attrs;

        if (isString(value)) {
            value = escapeAttr(value);
        }

        attrs.push({ name: name, value: value });
    };

    ElementSerializer.prototype.serializeAttributes = function(writer) {
        var attrs = this.attrs,
            namespaces = this.namespaces;

        if (namespaces) {
            attrs = getNsAttrs(namespaces).concat(attrs);
        }

        forEach(attrs, function(a) {
            writer
                .append(' ')
                .append(nsName(a.name)).append('="').append(a.value).append('"');
        });
    };

    ElementSerializer.prototype.serializeTo = function(writer) {
        var firstBody = this.body[0],
            indent = firstBody && firstBody.constructor !== BodySerializer;

        writer
            .appendIndent()
            .append('<' + this.tagName);

        this.serializeAttributes(writer);

        writer.append(firstBody ? '>' : ' />');

        if (firstBody) {

            if (indent) {
                writer
                    .appendNewLine()
                    .indent();
            }

            forEach(this.body, function(b) {
                b.serializeTo(writer);
            });

            if (indent) {
                writer
                    .unindent()
                    .appendIndent();
            }

            writer.append('</' + this.tagName + '>');
        }

        writer.appendNewLine();
    };

    /**
     * A serializer for types that handles serialization of data types
     */
    function TypeSerializer(parent, propertyDescriptor) {
        ElementSerializer.call(this, parent, propertyDescriptor);
    }

    inherits(TypeSerializer, ElementSerializer);

    TypeSerializer.prototype.parseNsAttributes = function(element) {

        // extracted attributes
        var attributes = ElementSerializer.prototype.parseNsAttributes.call(this, element);

        var descriptor = element.$descriptor;

        // only serialize xsi:type if necessary
        if (descriptor.name === this.propertyDescriptor.type) {
            return attributes;
        }

        var typeNs = this.typeNs = this.nsTagName(descriptor);
        this.getNamespaces().logUsed(this.typeNs);

        // add xsi:type attribute to represent the elements
        // actual type

        var pkg = element.$model.getPackage(typeNs.uri),
            typePrefix = (pkg.xml && pkg.xml.typePrefix) || '';

        this.addAttribute(
            this.nsAttributeName(XSI_TYPE$1),
            (typeNs.prefix ? typeNs.prefix + ':' : '') + typePrefix + descriptor.ns.localName
        );

        return attributes;
    };

    TypeSerializer.prototype.isLocalNs = function(ns) {
        return ns.uri === (this.typeNs || this.ns).uri;
    };

    function SavingWriter() {
        this.value = '';

        this.write = function(str) {
            this.value += str;
        };
    }

    function FormatingWriter(out, format) {

        var indent = [''];

        this.append = function(str) {
            out.write(str);

            return this;
        };

        this.appendNewLine = function() {
            if (format) {
                out.write('\n');
            }

            return this;
        };

        this.appendIndent = function() {
            if (format) {
                out.write(indent.join('  '));
            }

            return this;
        };

        this.indent = function() {
            indent.push('');
            return this;
        };

        this.unindent = function() {
            indent.pop();
            return this;
        };
    }

    /**
     * A writer for meta-model backed document trees
     *
     * @param {Object} options output options to pass into the writer
     */
    function Writer(options) {

        options = assign({ format: false, preamble: true }, options || {});

        function toXML(tree, writer) {
            var internalWriter = writer || new SavingWriter();
            var formatingWriter = new FormatingWriter(internalWriter, options.format);

            if (options.preamble) {
                formatingWriter.append(XML_PREAMBLE);
            }

            new ElementSerializer().build(tree).serializeTo(formatingWriter);

            if (!writer) {
                return internalWriter.value;
            }
        }

        return {
            toXML: toXML
        };
    }

    /**
     * A sub class of {@link Moddle} with support for import and export of BPMN 2.0 xml files.
     *
     * @class BpmnModdle
     * @extends Moddle
     *
     * @param {Object|Array} packages to use for instantiating the model
     * @param {Object} [options] additional options to pass over
     */
    function BpmnModdle(packages, options) {
        Moddle.call(this, packages, options);
    }

    BpmnModdle.prototype = Object.create(Moddle.prototype);


    /**
     * Instantiates a BPMN model tree from a given xml string.
     *
     * @param {String}   xmlStr
     * @param {String}   [typeName='bpmn:Definitions'] name of the root element
     * @param {Object}   [options]  options to pass to the underlying reader
     * @param {Function} done       callback that is invoked with (err, result, parseContext)
     *                              once the import completes
     */
    BpmnModdle.prototype.fromXML = function(xmlStr, typeName, options, done) {

        if (!isString(typeName)) {
            done = options;
            options = typeName;
            typeName = 'bpmn:Definitions';
        }

        if (isFunction(options)) {
            done = options;
            options = {};
        }

        var reader = new Reader(assign({ model: this, lax: true }, options));
        var rootHandler = reader.handler(typeName);

        reader.fromXML(xmlStr, rootHandler, done);
    };


    /**
     * Serializes a BPMN 2.0 object tree to XML.
     *
     * @param {String}   element    the root element, typically an instance of `bpmn:Definitions`
     * @param {Object}   [options]  to pass to the underlying writer
     * @param {Function} done       callback invoked with (err, xmlStr) once the import completes
     */
    BpmnModdle.prototype.toXML = function(element, options, done) {

        if (isFunction(options)) {
            done = options;
            options = {};
        }

        var writer = new Writer(options);

        var result;
        var err;

        try {
            result = writer.toXML(element);
        } catch (e) {
            err = e;
        }

        return done(err, result);
    };

    var name = "BPMN20";
    var uri = "http://www.omg.org/spec/BPMN/20100524/MODEL";
    var associations = [
    ];
    var types$1 = [
        {
            name: "Interface",
            superClass: [
                "RootElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "operations",
                    type: "Operation",
                    isMany: true
                },
                {
                    name: "implementationRef",
                    type: "String",
                    isAttr: true
                }
            ]
        },
        {
            name: "Operation",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "inMessageRef",
                    type: "Message",
                    isReference: true
                },
                {
                    name: "outMessageRef",
                    type: "Message",
                    isReference: true
                },
                {
                    name: "errorRef",
                    type: "Error",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "implementationRef",
                    type: "String",
                    isAttr: true
                }
            ]
        },
        {
            name: "EndPoint",
            superClass: [
                "RootElement"
            ]
        },
        {
            name: "Auditing",
            superClass: [
                "BaseElement"
            ]
        },
        {
            name: "GlobalTask",
            superClass: [
                "CallableElement"
            ],
            properties: [
                {
                    name: "resources",
                    type: "ResourceRole",
                    isMany: true
                }
            ]
        },
        {
            name: "Monitoring",
            superClass: [
                "BaseElement"
            ]
        },
        {
            name: "Performer",
            superClass: [
                "ResourceRole"
            ]
        },
        {
            name: "Process",
            superClass: [
                "FlowElementsContainer",
                "CallableElement"
            ],
            properties: [
                {
                    name: "processType",
                    type: "ProcessType",
                    isAttr: true
                },
                {
                    name: "isClosed",
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "auditing",
                    type: "Auditing"
                },
                {
                    name: "monitoring",
                    type: "Monitoring"
                },
                {
                    name: "properties",
                    type: "Property",
                    isMany: true
                },
                {
                    name: "laneSets",
                    type: "LaneSet",
                    isMany: true,
                    replaces: "FlowElementsContainer#laneSets"
                },
                {
                    name: "flowElements",
                    type: "FlowElement",
                    isMany: true,
                    replaces: "FlowElementsContainer#flowElements"
                },
                {
                    name: "artifacts",
                    type: "Artifact",
                    isMany: true
                },
                {
                    name: "resources",
                    type: "ResourceRole",
                    isMany: true
                },
                {
                    name: "correlationSubscriptions",
                    type: "CorrelationSubscription",
                    isMany: true
                },
                {
                    name: "supports",
                    type: "Process",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "definitionalCollaborationRef",
                    type: "Collaboration",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "isExecutable",
                    isAttr: true,
                    type: "Boolean"
                }
            ]
        },
        {
            name: "LaneSet",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "lanes",
                    type: "Lane",
                    isMany: true
                },
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "Lane",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "partitionElementRef",
                    type: "BaseElement",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "partitionElement",
                    type: "BaseElement"
                },
                {
                    name: "flowNodeRef",
                    type: "FlowNode",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "childLaneSet",
                    type: "LaneSet",
                    xml: {
                        serialize: "xsi:type"
                    }
                }
            ]
        },
        {
            name: "GlobalManualTask",
            superClass: [
                "GlobalTask"
            ]
        },
        {
            name: "ManualTask",
            superClass: [
                "Task"
            ]
        },
        {
            name: "UserTask",
            superClass: [
                "Task"
            ],
            properties: [
                {
                    name: "renderings",
                    type: "Rendering",
                    isMany: true
                },
                {
                    name: "implementation",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "Rendering",
            superClass: [
                "BaseElement"
            ]
        },
        {
            name: "HumanPerformer",
            superClass: [
                "Performer"
            ]
        },
        {
            name: "PotentialOwner",
            superClass: [
                "HumanPerformer"
            ]
        },
        {
            name: "GlobalUserTask",
            superClass: [
                "GlobalTask"
            ],
            properties: [
                {
                    name: "implementation",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "renderings",
                    type: "Rendering",
                    isMany: true
                }
            ]
        },
        {
            name: "Gateway",
            isAbstract: true,
            superClass: [
                "FlowNode"
            ],
            properties: [
                {
                    name: "gatewayDirection",
                    type: "GatewayDirection",
                    "default": "Unspecified",
                    isAttr: true
                }
            ]
        },
        {
            name: "EventBasedGateway",
            superClass: [
                "Gateway"
            ],
            properties: [
                {
                    name: "instantiate",
                    "default": false,
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "eventGatewayType",
                    type: "EventBasedGatewayType",
                    isAttr: true,
                    "default": "Exclusive"
                }
            ]
        },
        {
            name: "ComplexGateway",
            superClass: [
                "Gateway"
            ],
            properties: [
                {
                    name: "activationCondition",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                },
                {
                    name: "default",
                    type: "SequenceFlow",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "ExclusiveGateway",
            superClass: [
                "Gateway"
            ],
            properties: [
                {
                    name: "default",
                    type: "SequenceFlow",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "InclusiveGateway",
            superClass: [
                "Gateway"
            ],
            properties: [
                {
                    name: "default",
                    type: "SequenceFlow",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "ParallelGateway",
            superClass: [
                "Gateway"
            ]
        },
        {
            name: "RootElement",
            isAbstract: true,
            superClass: [
                "BaseElement"
            ]
        },
        {
            name: "Relationship",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "type",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "direction",
                    type: "RelationshipDirection",
                    isAttr: true
                },
                {
                    name: "source",
                    isMany: true,
                    isReference: true,
                    type: "Element"
                },
                {
                    name: "target",
                    isMany: true,
                    isReference: true,
                    type: "Element"
                }
            ]
        },
        {
            name: "BaseElement",
            isAbstract: true,
            properties: [
                {
                    name: "id",
                    isAttr: true,
                    type: "String",
                    isId: true
                },
                {
                    name: "documentation",
                    type: "Documentation",
                    isMany: true
                },
                {
                    name: "extensionDefinitions",
                    type: "ExtensionDefinition",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "extensionElements",
                    type: "ExtensionElements"
                }
            ]
        },
        {
            name: "Extension",
            properties: [
                {
                    name: "mustUnderstand",
                    "default": false,
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "definition",
                    type: "ExtensionDefinition",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "ExtensionDefinition",
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "extensionAttributeDefinitions",
                    type: "ExtensionAttributeDefinition",
                    isMany: true
                }
            ]
        },
        {
            name: "ExtensionAttributeDefinition",
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "type",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "isReference",
                    "default": false,
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "extensionDefinition",
                    type: "ExtensionDefinition",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "ExtensionElements",
            properties: [
                {
                    name: "valueRef",
                    isAttr: true,
                    isReference: true,
                    type: "Element"
                },
                {
                    name: "values",
                    type: "Element",
                    isMany: true
                },
                {
                    name: "extensionAttributeDefinition",
                    type: "ExtensionAttributeDefinition",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "Documentation",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "text",
                    type: "String",
                    isBody: true
                },
                {
                    name: "textFormat",
                    "default": "text/plain",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "Event",
            isAbstract: true,
            superClass: [
                "FlowNode",
                "InteractionNode"
            ],
            properties: [
                {
                    name: "properties",
                    type: "Property",
                    isMany: true
                }
            ]
        },
        {
            name: "IntermediateCatchEvent",
            superClass: [
                "CatchEvent"
            ]
        },
        {
            name: "IntermediateThrowEvent",
            superClass: [
                "ThrowEvent"
            ]
        },
        {
            name: "EndEvent",
            superClass: [
                "ThrowEvent"
            ]
        },
        {
            name: "StartEvent",
            superClass: [
                "CatchEvent"
            ],
            properties: [
                {
                    name: "isInterrupting",
                    "default": true,
                    isAttr: true,
                    type: "Boolean"
                }
            ]
        },
        {
            name: "ThrowEvent",
            isAbstract: true,
            superClass: [
                "Event"
            ],
            properties: [
                {
                    name: "dataInputs",
                    type: "DataInput",
                    isMany: true
                },
                {
                    name: "dataInputAssociations",
                    type: "DataInputAssociation",
                    isMany: true
                },
                {
                    name: "inputSet",
                    type: "InputSet"
                },
                {
                    name: "eventDefinitions",
                    type: "EventDefinition",
                    isMany: true
                },
                {
                    name: "eventDefinitionRef",
                    type: "EventDefinition",
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "CatchEvent",
            isAbstract: true,
            superClass: [
                "Event"
            ],
            properties: [
                {
                    name: "parallelMultiple",
                    isAttr: true,
                    type: "Boolean",
                    "default": false
                },
                {
                    name: "dataOutputs",
                    type: "DataOutput",
                    isMany: true
                },
                {
                    name: "dataOutputAssociations",
                    type: "DataOutputAssociation",
                    isMany: true
                },
                {
                    name: "outputSet",
                    type: "OutputSet"
                },
                {
                    name: "eventDefinitions",
                    type: "EventDefinition",
                    isMany: true
                },
                {
                    name: "eventDefinitionRef",
                    type: "EventDefinition",
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "BoundaryEvent",
            superClass: [
                "CatchEvent"
            ],
            properties: [
                {
                    name: "cancelActivity",
                    "default": true,
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "attachedToRef",
                    type: "Activity",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "EventDefinition",
            isAbstract: true,
            superClass: [
                "RootElement"
            ]
        },
        {
            name: "CancelEventDefinition",
            superClass: [
                "EventDefinition"
            ]
        },
        {
            name: "ErrorEventDefinition",
            superClass: [
                "EventDefinition"
            ],
            properties: [
                {
                    name: "errorRef",
                    type: "Error",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "TerminateEventDefinition",
            superClass: [
                "EventDefinition"
            ]
        },
        {
            name: "EscalationEventDefinition",
            superClass: [
                "EventDefinition"
            ],
            properties: [
                {
                    name: "escalationRef",
                    type: "Escalation",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "Escalation",
            properties: [
                {
                    name: "structureRef",
                    type: "ItemDefinition",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "escalationCode",
                    isAttr: true,
                    type: "String"
                }
            ],
            superClass: [
                "RootElement"
            ]
        },
        {
            name: "CompensateEventDefinition",
            superClass: [
                "EventDefinition"
            ],
            properties: [
                {
                    name: "waitForCompletion",
                    isAttr: true,
                    type: "Boolean",
                    "default": true
                },
                {
                    name: "activityRef",
                    type: "Activity",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "TimerEventDefinition",
            superClass: [
                "EventDefinition"
            ],
            properties: [
                {
                    name: "timeDate",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                },
                {
                    name: "timeCycle",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                },
                {
                    name: "timeDuration",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                }
            ]
        },
        {
            name: "LinkEventDefinition",
            superClass: [
                "EventDefinition"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "target",
                    type: "LinkEventDefinition",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "source",
                    type: "LinkEventDefinition",
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "MessageEventDefinition",
            superClass: [
                "EventDefinition"
            ],
            properties: [
                {
                    name: "messageRef",
                    type: "Message",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "operationRef",
                    type: "Operation",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "ConditionalEventDefinition",
            superClass: [
                "EventDefinition"
            ],
            properties: [
                {
                    name: "condition",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                }
            ]
        },
        {
            name: "SignalEventDefinition",
            superClass: [
                "EventDefinition"
            ],
            properties: [
                {
                    name: "signalRef",
                    type: "Signal",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "Signal",
            superClass: [
                "RootElement"
            ],
            properties: [
                {
                    name: "structureRef",
                    type: "ItemDefinition",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "ImplicitThrowEvent",
            superClass: [
                "ThrowEvent"
            ]
        },
        {
            name: "DataState",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "ItemAwareElement",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "itemSubjectRef",
                    type: "ItemDefinition",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "dataState",
                    type: "DataState"
                }
            ]
        },
        {
            name: "DataAssociation",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "assignment",
                    type: "Assignment",
                    isMany: true
                },
                {
                    name: "sourceRef",
                    type: "ItemAwareElement",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "targetRef",
                    type: "ItemAwareElement",
                    isReference: true
                },
                {
                    name: "transformation",
                    type: "FormalExpression",
                    xml: {
                        serialize: "property"
                    }
                }
            ]
        },
        {
            name: "DataInput",
            superClass: [
                "ItemAwareElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "isCollection",
                    "default": false,
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "inputSetRef",
                    type: "InputSet",
                    isVirtual: true,
                    isMany: true,
                    isReference: true
                },
                {
                    name: "inputSetWithOptional",
                    type: "InputSet",
                    isVirtual: true,
                    isMany: true,
                    isReference: true
                },
                {
                    name: "inputSetWithWhileExecuting",
                    type: "InputSet",
                    isVirtual: true,
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "DataOutput",
            superClass: [
                "ItemAwareElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "isCollection",
                    "default": false,
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "outputSetRef",
                    type: "OutputSet",
                    isVirtual: true,
                    isMany: true,
                    isReference: true
                },
                {
                    name: "outputSetWithOptional",
                    type: "OutputSet",
                    isVirtual: true,
                    isMany: true,
                    isReference: true
                },
                {
                    name: "outputSetWithWhileExecuting",
                    type: "OutputSet",
                    isVirtual: true,
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "InputSet",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "dataInputRefs",
                    type: "DataInput",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "optionalInputRefs",
                    type: "DataInput",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "whileExecutingInputRefs",
                    type: "DataInput",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "outputSetRefs",
                    type: "OutputSet",
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "OutputSet",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "dataOutputRefs",
                    type: "DataOutput",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "inputSetRefs",
                    type: "InputSet",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "optionalOutputRefs",
                    type: "DataOutput",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "whileExecutingOutputRefs",
                    type: "DataOutput",
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "Property",
            superClass: [
                "ItemAwareElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "DataInputAssociation",
            superClass: [
                "DataAssociation"
            ]
        },
        {
            name: "DataOutputAssociation",
            superClass: [
                "DataAssociation"
            ]
        },
        {
            name: "InputOutputSpecification",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "dataInputs",
                    type: "DataInput",
                    isMany: true
                },
                {
                    name: "dataOutputs",
                    type: "DataOutput",
                    isMany: true
                },
                {
                    name: "inputSets",
                    type: "InputSet",
                    isMany: true
                },
                {
                    name: "outputSets",
                    type: "OutputSet",
                    isMany: true
                }
            ]
        },
        {
            name: "DataObject",
            superClass: [
                "FlowElement",
                "ItemAwareElement"
            ],
            properties: [
                {
                    name: "isCollection",
                    "default": false,
                    isAttr: true,
                    type: "Boolean"
                }
            ]
        },
        {
            name: "InputOutputBinding",
            properties: [
                {
                    name: "inputDataRef",
                    type: "InputSet",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "outputDataRef",
                    type: "OutputSet",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "operationRef",
                    type: "Operation",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "Assignment",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "from",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                },
                {
                    name: "to",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                }
            ]
        },
        {
            name: "DataStore",
            superClass: [
                "RootElement",
                "ItemAwareElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "capacity",
                    isAttr: true,
                    type: "Integer"
                },
                {
                    name: "isUnlimited",
                    "default": true,
                    isAttr: true,
                    type: "Boolean"
                }
            ]
        },
        {
            name: "DataStoreReference",
            superClass: [
                "ItemAwareElement",
                "FlowElement"
            ],
            properties: [
                {
                    name: "dataStoreRef",
                    type: "DataStore",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "DataObjectReference",
            superClass: [
                "ItemAwareElement",
                "FlowElement"
            ],
            properties: [
                {
                    name: "dataObjectRef",
                    type: "DataObject",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "ConversationLink",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "sourceRef",
                    type: "InteractionNode",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "targetRef",
                    type: "InteractionNode",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "ConversationAssociation",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "innerConversationNodeRef",
                    type: "ConversationNode",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "outerConversationNodeRef",
                    type: "ConversationNode",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "CallConversation",
            superClass: [
                "ConversationNode"
            ],
            properties: [
                {
                    name: "calledCollaborationRef",
                    type: "Collaboration",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "participantAssociations",
                    type: "ParticipantAssociation",
                    isMany: true
                }
            ]
        },
        {
            name: "Conversation",
            superClass: [
                "ConversationNode"
            ]
        },
        {
            name: "SubConversation",
            superClass: [
                "ConversationNode"
            ],
            properties: [
                {
                    name: "conversationNodes",
                    type: "ConversationNode",
                    isMany: true
                }
            ]
        },
        {
            name: "ConversationNode",
            isAbstract: true,
            superClass: [
                "InteractionNode",
                "BaseElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "participantRef",
                    type: "Participant",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "messageFlowRefs",
                    type: "MessageFlow",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "correlationKeys",
                    type: "CorrelationKey",
                    isMany: true
                }
            ]
        },
        {
            name: "GlobalConversation",
            superClass: [
                "Collaboration"
            ]
        },
        {
            name: "PartnerEntity",
            superClass: [
                "RootElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "participantRef",
                    type: "Participant",
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "PartnerRole",
            superClass: [
                "RootElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "participantRef",
                    type: "Participant",
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "CorrelationProperty",
            superClass: [
                "RootElement"
            ],
            properties: [
                {
                    name: "correlationPropertyRetrievalExpression",
                    type: "CorrelationPropertyRetrievalExpression",
                    isMany: true
                },
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "type",
                    type: "ItemDefinition",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "Error",
            superClass: [
                "RootElement"
            ],
            properties: [
                {
                    name: "structureRef",
                    type: "ItemDefinition",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "errorCode",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "CorrelationKey",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "correlationPropertyRef",
                    type: "CorrelationProperty",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "Expression",
            superClass: [
                "BaseElement"
            ],
            isAbstract: false,
            properties: [
                {
                    name: "body",
                    type: "String",
                    isBody: true
                }
            ]
        },
        {
            name: "FormalExpression",
            superClass: [
                "Expression"
            ],
            properties: [
                {
                    name: "language",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "evaluatesToTypeRef",
                    type: "ItemDefinition",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "Message",
            superClass: [
                "RootElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "itemRef",
                    type: "ItemDefinition",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "ItemDefinition",
            superClass: [
                "RootElement"
            ],
            properties: [
                {
                    name: "itemKind",
                    type: "ItemKind",
                    isAttr: true
                },
                {
                    name: "structureRef",
                    type: "String",
                    isAttr: true
                },
                {
                    name: "isCollection",
                    "default": false,
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "import",
                    type: "Import",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "FlowElement",
            isAbstract: true,
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "auditing",
                    type: "Auditing"
                },
                {
                    name: "monitoring",
                    type: "Monitoring"
                },
                {
                    name: "categoryValueRef",
                    type: "CategoryValue",
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "SequenceFlow",
            superClass: [
                "FlowElement"
            ],
            properties: [
                {
                    name: "isImmediate",
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "conditionExpression",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                },
                {
                    name: "sourceRef",
                    type: "FlowNode",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "targetRef",
                    type: "FlowNode",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "FlowElementsContainer",
            isAbstract: true,
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "laneSets",
                    type: "LaneSet",
                    isMany: true
                },
                {
                    name: "flowElements",
                    type: "FlowElement",
                    isMany: true
                }
            ]
        },
        {
            name: "CallableElement",
            isAbstract: true,
            superClass: [
                "RootElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "ioSpecification",
                    type: "InputOutputSpecification",
                    xml: {
                        serialize: "property"
                    }
                },
                {
                    name: "supportedInterfaceRef",
                    type: "Interface",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "ioBinding",
                    type: "InputOutputBinding",
                    isMany: true,
                    xml: {
                        serialize: "property"
                    }
                }
            ]
        },
        {
            name: "FlowNode",
            isAbstract: true,
            superClass: [
                "FlowElement"
            ],
            properties: [
                {
                    name: "incoming",
                    type: "SequenceFlow",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "outgoing",
                    type: "SequenceFlow",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "lanes",
                    type: "Lane",
                    isVirtual: true,
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "CorrelationPropertyRetrievalExpression",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "messagePath",
                    type: "FormalExpression"
                },
                {
                    name: "messageRef",
                    type: "Message",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "CorrelationPropertyBinding",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "dataPath",
                    type: "FormalExpression"
                },
                {
                    name: "correlationPropertyRef",
                    type: "CorrelationProperty",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "Resource",
            superClass: [
                "RootElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "resourceParameters",
                    type: "ResourceParameter",
                    isMany: true
                }
            ]
        },
        {
            name: "ResourceParameter",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "isRequired",
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "type",
                    type: "ItemDefinition",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "CorrelationSubscription",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "correlationKeyRef",
                    type: "CorrelationKey",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "correlationPropertyBinding",
                    type: "CorrelationPropertyBinding",
                    isMany: true
                }
            ]
        },
        {
            name: "MessageFlow",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "sourceRef",
                    type: "InteractionNode",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "targetRef",
                    type: "InteractionNode",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "messageRef",
                    type: "Message",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "MessageFlowAssociation",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "innerMessageFlowRef",
                    type: "MessageFlow",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "outerMessageFlowRef",
                    type: "MessageFlow",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "InteractionNode",
            isAbstract: true,
            properties: [
                {
                    name: "incomingConversationLinks",
                    type: "ConversationLink",
                    isVirtual: true,
                    isMany: true,
                    isReference: true
                },
                {
                    name: "outgoingConversationLinks",
                    type: "ConversationLink",
                    isVirtual: true,
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "Participant",
            superClass: [
                "InteractionNode",
                "BaseElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "interfaceRef",
                    type: "Interface",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "participantMultiplicity",
                    type: "ParticipantMultiplicity"
                },
                {
                    name: "endPointRefs",
                    type: "EndPoint",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "processRef",
                    type: "Process",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "ParticipantAssociation",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "innerParticipantRef",
                    type: "Participant",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "outerParticipantRef",
                    type: "Participant",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "ParticipantMultiplicity",
            properties: [
                {
                    name: "minimum",
                    "default": 0,
                    isAttr: true,
                    type: "Integer"
                },
                {
                    name: "maximum",
                    "default": 1,
                    isAttr: true,
                    type: "Integer"
                }
            ],
            superClass: [
                "BaseElement"
            ]
        },
        {
            name: "Collaboration",
            superClass: [
                "RootElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "isClosed",
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "participants",
                    type: "Participant",
                    isMany: true
                },
                {
                    name: "messageFlows",
                    type: "MessageFlow",
                    isMany: true
                },
                {
                    name: "artifacts",
                    type: "Artifact",
                    isMany: true
                },
                {
                    name: "conversations",
                    type: "ConversationNode",
                    isMany: true
                },
                {
                    name: "conversationAssociations",
                    type: "ConversationAssociation"
                },
                {
                    name: "participantAssociations",
                    type: "ParticipantAssociation",
                    isMany: true
                },
                {
                    name: "messageFlowAssociations",
                    type: "MessageFlowAssociation",
                    isMany: true
                },
                {
                    name: "correlationKeys",
                    type: "CorrelationKey",
                    isMany: true
                },
                {
                    name: "choreographyRef",
                    type: "Choreography",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "conversationLinks",
                    type: "ConversationLink",
                    isMany: true
                }
            ]
        },
        {
            name: "ChoreographyActivity",
            isAbstract: true,
            superClass: [
                "FlowNode"
            ],
            properties: [
                {
                    name: "participantRef",
                    type: "Participant",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "initiatingParticipantRef",
                    type: "Participant",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "correlationKeys",
                    type: "CorrelationKey",
                    isMany: true
                },
                {
                    name: "loopType",
                    type: "ChoreographyLoopType",
                    "default": "None",
                    isAttr: true
                }
            ]
        },
        {
            name: "CallChoreography",
            superClass: [
                "ChoreographyActivity"
            ],
            properties: [
                {
                    name: "calledChoreographyRef",
                    type: "Choreography",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "participantAssociations",
                    type: "ParticipantAssociation",
                    isMany: true
                }
            ]
        },
        {
            name: "SubChoreography",
            superClass: [
                "ChoreographyActivity",
                "FlowElementsContainer"
            ],
            properties: [
                {
                    name: "artifacts",
                    type: "Artifact",
                    isMany: true
                }
            ]
        },
        {
            name: "ChoreographyTask",
            superClass: [
                "ChoreographyActivity"
            ],
            properties: [
                {
                    name: "messageFlowRef",
                    type: "MessageFlow",
                    isMany: true,
                    isReference: true
                }
            ]
        },
        {
            name: "Choreography",
            superClass: [
                "Collaboration",
                "FlowElementsContainer"
            ]
        },
        {
            name: "GlobalChoreographyTask",
            superClass: [
                "Choreography"
            ],
            properties: [
                {
                    name: "initiatingParticipantRef",
                    type: "Participant",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "TextAnnotation",
            superClass: [
                "Artifact"
            ],
            properties: [
                {
                    name: "text",
                    type: "String"
                },
                {
                    name: "textFormat",
                    "default": "text/plain",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "Group",
            superClass: [
                "Artifact"
            ],
            properties: [
                {
                    name: "categoryValueRef",
                    type: "CategoryValue",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "Association",
            superClass: [
                "Artifact"
            ],
            properties: [
                {
                    name: "associationDirection",
                    type: "AssociationDirection",
                    isAttr: true
                },
                {
                    name: "sourceRef",
                    type: "BaseElement",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "targetRef",
                    type: "BaseElement",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "Category",
            superClass: [
                "RootElement"
            ],
            properties: [
                {
                    name: "categoryValue",
                    type: "CategoryValue",
                    isMany: true
                },
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "Artifact",
            isAbstract: true,
            superClass: [
                "BaseElement"
            ]
        },
        {
            name: "CategoryValue",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "categorizedFlowElements",
                    type: "FlowElement",
                    isVirtual: true,
                    isMany: true,
                    isReference: true
                },
                {
                    name: "value",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "Activity",
            isAbstract: true,
            superClass: [
                "FlowNode"
            ],
            properties: [
                {
                    name: "isForCompensation",
                    "default": false,
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "default",
                    type: "SequenceFlow",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "ioSpecification",
                    type: "InputOutputSpecification",
                    xml: {
                        serialize: "property"
                    }
                },
                {
                    name: "boundaryEventRefs",
                    type: "BoundaryEvent",
                    isMany: true,
                    isReference: true
                },
                {
                    name: "properties",
                    type: "Property",
                    isMany: true
                },
                {
                    name: "dataInputAssociations",
                    type: "DataInputAssociation",
                    isMany: true
                },
                {
                    name: "dataOutputAssociations",
                    type: "DataOutputAssociation",
                    isMany: true
                },
                {
                    name: "startQuantity",
                    "default": 1,
                    isAttr: true,
                    type: "Integer"
                },
                {
                    name: "resources",
                    type: "ResourceRole",
                    isMany: true
                },
                {
                    name: "completionQuantity",
                    "default": 1,
                    isAttr: true,
                    type: "Integer"
                },
                {
                    name: "loopCharacteristics",
                    type: "LoopCharacteristics"
                }
            ]
        },
        {
            name: "ServiceTask",
            superClass: [
                "Task"
            ],
            properties: [
                {
                    name: "implementation",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "operationRef",
                    type: "Operation",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "SubProcess",
            superClass: [
                "Activity",
                "FlowElementsContainer",
                "InteractionNode"
            ],
            properties: [
                {
                    name: "triggeredByEvent",
                    "default": false,
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "artifacts",
                    type: "Artifact",
                    isMany: true
                }
            ]
        },
        {
            name: "LoopCharacteristics",
            isAbstract: true,
            superClass: [
                "BaseElement"
            ]
        },
        {
            name: "MultiInstanceLoopCharacteristics",
            superClass: [
                "LoopCharacteristics"
            ],
            properties: [
                {
                    name: "isSequential",
                    "default": false,
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "behavior",
                    type: "MultiInstanceBehavior",
                    "default": "All",
                    isAttr: true
                },
                {
                    name: "loopCardinality",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                },
                {
                    name: "loopDataInputRef",
                    type: "ItemAwareElement",
                    isReference: true
                },
                {
                    name: "loopDataOutputRef",
                    type: "ItemAwareElement",
                    isReference: true
                },
                {
                    name: "inputDataItem",
                    type: "DataInput",
                    xml: {
                        serialize: "property"
                    }
                },
                {
                    name: "outputDataItem",
                    type: "DataOutput",
                    xml: {
                        serialize: "property"
                    }
                },
                {
                    name: "complexBehaviorDefinition",
                    type: "ComplexBehaviorDefinition",
                    isMany: true
                },
                {
                    name: "completionCondition",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                },
                {
                    name: "oneBehaviorEventRef",
                    type: "EventDefinition",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "noneBehaviorEventRef",
                    type: "EventDefinition",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "StandardLoopCharacteristics",
            superClass: [
                "LoopCharacteristics"
            ],
            properties: [
                {
                    name: "testBefore",
                    "default": false,
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "loopCondition",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                },
                {
                    name: "loopMaximum",
                    type: "Integer",
                    isAttr: true
                }
            ]
        },
        {
            name: "CallActivity",
            superClass: [
                "Activity"
            ],
            properties: [
                {
                    name: "calledElement",
                    type: "String",
                    isAttr: true
                }
            ]
        },
        {
            name: "Task",
            superClass: [
                "Activity",
                "InteractionNode"
            ]
        },
        {
            name: "SendTask",
            superClass: [
                "Task"
            ],
            properties: [
                {
                    name: "implementation",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "operationRef",
                    type: "Operation",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "messageRef",
                    type: "Message",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "ReceiveTask",
            superClass: [
                "Task"
            ],
            properties: [
                {
                    name: "implementation",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "instantiate",
                    "default": false,
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "operationRef",
                    type: "Operation",
                    isAttr: true,
                    isReference: true
                },
                {
                    name: "messageRef",
                    type: "Message",
                    isAttr: true,
                    isReference: true
                }
            ]
        },
        {
            name: "ScriptTask",
            superClass: [
                "Task"
            ],
            properties: [
                {
                    name: "scriptFormat",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "script",
                    type: "String"
                }
            ]
        },
        {
            name: "BusinessRuleTask",
            superClass: [
                "Task"
            ],
            properties: [
                {
                    name: "implementation",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "AdHocSubProcess",
            superClass: [
                "SubProcess"
            ],
            properties: [
                {
                    name: "completionCondition",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                },
                {
                    name: "ordering",
                    type: "AdHocOrdering",
                    isAttr: true
                },
                {
                    name: "cancelRemainingInstances",
                    "default": true,
                    isAttr: true,
                    type: "Boolean"
                }
            ]
        },
        {
            name: "Transaction",
            superClass: [
                "SubProcess"
            ],
            properties: [
                {
                    name: "protocol",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "method",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "GlobalScriptTask",
            superClass: [
                "GlobalTask"
            ],
            properties: [
                {
                    name: "scriptLanguage",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "script",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "GlobalBusinessRuleTask",
            superClass: [
                "GlobalTask"
            ],
            properties: [
                {
                    name: "implementation",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "ComplexBehaviorDefinition",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "condition",
                    type: "FormalExpression"
                },
                {
                    name: "event",
                    type: "ImplicitThrowEvent"
                }
            ]
        },
        {
            name: "ResourceRole",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "resourceRef",
                    type: "Resource",
                    isReference: true
                },
                {
                    name: "resourceParameterBindings",
                    type: "ResourceParameterBinding",
                    isMany: true
                },
                {
                    name: "resourceAssignmentExpression",
                    type: "ResourceAssignmentExpression"
                },
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "ResourceParameterBinding",
            properties: [
                {
                    name: "expression",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                },
                {
                    name: "parameterRef",
                    type: "ResourceParameter",
                    isAttr: true,
                    isReference: true
                }
            ],
            superClass: [
                "BaseElement"
            ]
        },
        {
            name: "ResourceAssignmentExpression",
            properties: [
                {
                    name: "expression",
                    type: "Expression",
                    xml: {
                        serialize: "xsi:type"
                    }
                }
            ],
            superClass: [
                "BaseElement"
            ]
        },
        {
            name: "Import",
            properties: [
                {
                    name: "importType",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "location",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "namespace",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "Definitions",
            superClass: [
                "BaseElement"
            ],
            properties: [
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "targetNamespace",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "expressionLanguage",
                    "default": "http://www.w3.org/1999/XPath",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "typeLanguage",
                    "default": "http://www.w3.org/2001/XMLSchema",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "imports",
                    type: "Import",
                    isMany: true
                },
                {
                    name: "extensions",
                    type: "Extension",
                    isMany: true
                },
                {
                    name: "rootElements",
                    type: "RootElement",
                    isMany: true
                },
                {
                    name: "diagrams",
                    isMany: true,
                    type: "bpmndi:BPMNDiagram"
                },
                {
                    name: "exporter",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "relationships",
                    type: "Relationship",
                    isMany: true
                },
                {
                    name: "exporterVersion",
                    isAttr: true,
                    type: "String"
                }
            ]
        }
    ];
    var enumerations = [
        {
            name: "ProcessType",
            literalValues: [
                {
                    name: "None"
                },
                {
                    name: "Public"
                },
                {
                    name: "Private"
                }
            ]
        },
        {
            name: "GatewayDirection",
            literalValues: [
                {
                    name: "Unspecified"
                },
                {
                    name: "Converging"
                },
                {
                    name: "Diverging"
                },
                {
                    name: "Mixed"
                }
            ]
        },
        {
            name: "EventBasedGatewayType",
            literalValues: [
                {
                    name: "Parallel"
                },
                {
                    name: "Exclusive"
                }
            ]
        },
        {
            name: "RelationshipDirection",
            literalValues: [
                {
                    name: "None"
                },
                {
                    name: "Forward"
                },
                {
                    name: "Backward"
                },
                {
                    name: "Both"
                }
            ]
        },
        {
            name: "ItemKind",
            literalValues: [
                {
                    name: "Physical"
                },
                {
                    name: "Information"
                }
            ]
        },
        {
            name: "ChoreographyLoopType",
            literalValues: [
                {
                    name: "None"
                },
                {
                    name: "Standard"
                },
                {
                    name: "MultiInstanceSequential"
                },
                {
                    name: "MultiInstanceParallel"
                }
            ]
        },
        {
            name: "AssociationDirection",
            literalValues: [
                {
                    name: "None"
                },
                {
                    name: "One"
                },
                {
                    name: "Both"
                }
            ]
        },
        {
            name: "MultiInstanceBehavior",
            literalValues: [
                {
                    name: "None"
                },
                {
                    name: "One"
                },
                {
                    name: "All"
                },
                {
                    name: "Complex"
                }
            ]
        },
        {
            name: "AdHocOrdering",
            literalValues: [
                {
                    name: "Parallel"
                },
                {
                    name: "Sequential"
                }
            ]
        }
    ];
    var prefix$1 = "bpmn";
    var xml = {
        tagAlias: "lowerCase",
        typePrefix: "t"
    };
    var BpmnPackage = {
        name: name,
        uri: uri,
        associations: associations,
        types: types$1,
        enumerations: enumerations,
        prefix: prefix$1,
        xml: xml
    };

    var name$1 = "BPMNDI";
    var uri$1 = "http://www.omg.org/spec/BPMN/20100524/DI";
    var types$2 = [
        {
            name: "BPMNDiagram",
            properties: [
                {
                    name: "plane",
                    type: "BPMNPlane",
                    redefines: "di:Diagram#rootElement"
                },
                {
                    name: "labelStyle",
                    type: "BPMNLabelStyle",
                    isMany: true
                }
            ],
            superClass: [
                "di:Diagram"
            ]
        },
        {
            name: "BPMNPlane",
            properties: [
                {
                    name: "bpmnElement",
                    isAttr: true,
                    isReference: true,
                    type: "bpmn:BaseElement",
                    redefines: "di:DiagramElement#modelElement"
                }
            ],
            superClass: [
                "di:Plane"
            ]
        },
        {
            name: "BPMNShape",
            properties: [
                {
                    name: "bpmnElement",
                    isAttr: true,
                    isReference: true,
                    type: "bpmn:BaseElement",
                    redefines: "di:DiagramElement#modelElement"
                },
                {
                    name: "isHorizontal",
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "isExpanded",
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "isMarkerVisible",
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "label",
                    type: "BPMNLabel"
                },
                {
                    name: "isMessageVisible",
                    isAttr: true,
                    type: "Boolean"
                },
                {
                    name: "participantBandKind",
                    type: "ParticipantBandKind",
                    isAttr: true
                },
                {
                    name: "choreographyActivityShape",
                    type: "BPMNShape",
                    isAttr: true,
                    isReference: true
                }
            ],
            superClass: [
                "di:LabeledShape"
            ]
        },
        {
            name: "BPMNEdge",
            properties: [
                {
                    name: "label",
                    type: "BPMNLabel"
                },
                {
                    name: "bpmnElement",
                    isAttr: true,
                    isReference: true,
                    type: "bpmn:BaseElement",
                    redefines: "di:DiagramElement#modelElement"
                },
                {
                    name: "sourceElement",
                    isAttr: true,
                    isReference: true,
                    type: "di:DiagramElement",
                    redefines: "di:Edge#source"
                },
                {
                    name: "targetElement",
                    isAttr: true,
                    isReference: true,
                    type: "di:DiagramElement",
                    redefines: "di:Edge#target"
                },
                {
                    name: "messageVisibleKind",
                    type: "MessageVisibleKind",
                    isAttr: true,
                    "default": "initiating"
                }
            ],
            superClass: [
                "di:LabeledEdge"
            ]
        },
        {
            name: "BPMNLabel",
            properties: [
                {
                    name: "labelStyle",
                    type: "BPMNLabelStyle",
                    isAttr: true,
                    isReference: true,
                    redefines: "di:DiagramElement#style"
                }
            ],
            superClass: [
                "di:Label"
            ]
        },
        {
            name: "BPMNLabelStyle",
            properties: [
                {
                    name: "font",
                    type: "dc:Font"
                }
            ],
            superClass: [
                "di:Style"
            ]
        }
    ];
    var enumerations$1 = [
        {
            name: "ParticipantBandKind",
            literalValues: [
                {
                    name: "top_initiating"
                },
                {
                    name: "middle_initiating"
                },
                {
                    name: "bottom_initiating"
                },
                {
                    name: "top_non_initiating"
                },
                {
                    name: "middle_non_initiating"
                },
                {
                    name: "bottom_non_initiating"
                }
            ]
        },
        {
            name: "MessageVisibleKind",
            literalValues: [
                {
                    name: "initiating"
                },
                {
                    name: "non_initiating"
                }
            ]
        }
    ];
    var associations$1 = [
    ];
    var prefix$2 = "bpmndi";
    var BpmnDiPackage = {
        name: name$1,
        uri: uri$1,
        types: types$2,
        enumerations: enumerations$1,
        associations: associations$1,
        prefix: prefix$2
    };

    var name$2 = "DC";
    var uri$2 = "http://www.omg.org/spec/DD/20100524/DC";
    var types$3 = [
        {
            name: "Boolean"
        },
        {
            name: "Integer"
        },
        {
            name: "Real"
        },
        {
            name: "String"
        },
        {
            name: "Font",
            properties: [
                {
                    name: "name",
                    type: "String",
                    isAttr: true
                },
                {
                    name: "size",
                    type: "Real",
                    isAttr: true
                },
                {
                    name: "isBold",
                    type: "Boolean",
                    isAttr: true
                },
                {
                    name: "isItalic",
                    type: "Boolean",
                    isAttr: true
                },
                {
                    name: "isUnderline",
                    type: "Boolean",
                    isAttr: true
                },
                {
                    name: "isStrikeThrough",
                    type: "Boolean",
                    isAttr: true
                }
            ]
        },
        {
            name: "Point",
            properties: [
                {
                    name: "x",
                    type: "Real",
                    "default": "0",
                    isAttr: true
                },
                {
                    name: "y",
                    type: "Real",
                    "default": "0",
                    isAttr: true
                }
            ]
        },
        {
            name: "Bounds",
            properties: [
                {
                    name: "x",
                    type: "Real",
                    "default": "0",
                    isAttr: true
                },
                {
                    name: "y",
                    type: "Real",
                    "default": "0",
                    isAttr: true
                },
                {
                    name: "width",
                    type: "Real",
                    isAttr: true
                },
                {
                    name: "height",
                    type: "Real",
                    isAttr: true
                }
            ]
        }
    ];
    var prefix$3 = "dc";
    var associations$2 = [
    ];
    var DcPackage = {
        name: name$2,
        uri: uri$2,
        types: types$3,
        prefix: prefix$3,
        associations: associations$2
    };

    var name$3 = "DI";
    var uri$3 = "http://www.omg.org/spec/DD/20100524/DI";
    var types$4 = [
        {
            name: "DiagramElement",
            isAbstract: true,
            properties: [
                {
                    name: "id",
                    type: "String",
                    isAttr: true,
                    isId: true
                },
                {
                    name: "extension",
                    type: "Extension"
                },
                {
                    name: "owningDiagram",
                    type: "Diagram",
                    isReadOnly: true,
                    isVirtual: true,
                    isReference: true
                },
                {
                    name: "owningElement",
                    type: "DiagramElement",
                    isReadOnly: true,
                    isVirtual: true,
                    isReference: true
                },
                {
                    name: "modelElement",
                    isReadOnly: true,
                    isVirtual: true,
                    isReference: true,
                    type: "Element"
                },
                {
                    name: "style",
                    type: "Style",
                    isReadOnly: true,
                    isVirtual: true,
                    isReference: true
                },
                {
                    name: "ownedElement",
                    type: "DiagramElement",
                    isReadOnly: true,
                    isVirtual: true,
                    isMany: true
                }
            ]
        },
        {
            name: "Node",
            isAbstract: true,
            superClass: [
                "DiagramElement"
            ]
        },
        {
            name: "Edge",
            isAbstract: true,
            superClass: [
                "DiagramElement"
            ],
            properties: [
                {
                    name: "source",
                    type: "DiagramElement",
                    isReadOnly: true,
                    isVirtual: true,
                    isReference: true
                },
                {
                    name: "target",
                    type: "DiagramElement",
                    isReadOnly: true,
                    isVirtual: true,
                    isReference: true
                },
                {
                    name: "waypoint",
                    isUnique: false,
                    isMany: true,
                    type: "dc:Point",
                    xml: {
                        serialize: "xsi:type"
                    }
                }
            ]
        },
        {
            name: "Diagram",
            isAbstract: true,
            properties: [
                {
                    name: "id",
                    type: "String",
                    isAttr: true,
                    isId: true
                },
                {
                    name: "rootElement",
                    type: "DiagramElement",
                    isReadOnly: true,
                    isVirtual: true
                },
                {
                    name: "name",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "documentation",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "resolution",
                    isAttr: true,
                    type: "Real"
                },
                {
                    name: "ownedStyle",
                    type: "Style",
                    isReadOnly: true,
                    isVirtual: true,
                    isMany: true
                }
            ]
        },
        {
            name: "Shape",
            isAbstract: true,
            superClass: [
                "Node"
            ],
            properties: [
                {
                    name: "bounds",
                    type: "dc:Bounds"
                }
            ]
        },
        {
            name: "Plane",
            isAbstract: true,
            superClass: [
                "Node"
            ],
            properties: [
                {
                    name: "planeElement",
                    type: "DiagramElement",
                    subsettedProperty: "DiagramElement-ownedElement",
                    isMany: true
                }
            ]
        },
        {
            name: "LabeledEdge",
            isAbstract: true,
            superClass: [
                "Edge"
            ],
            properties: [
                {
                    name: "ownedLabel",
                    type: "Label",
                    isReadOnly: true,
                    subsettedProperty: "DiagramElement-ownedElement",
                    isVirtual: true,
                    isMany: true
                }
            ]
        },
        {
            name: "LabeledShape",
            isAbstract: true,
            superClass: [
                "Shape"
            ],
            properties: [
                {
                    name: "ownedLabel",
                    type: "Label",
                    isReadOnly: true,
                    subsettedProperty: "DiagramElement-ownedElement",
                    isVirtual: true,
                    isMany: true
                }
            ]
        },
        {
            name: "Label",
            isAbstract: true,
            superClass: [
                "Node"
            ],
            properties: [
                {
                    name: "bounds",
                    type: "dc:Bounds"
                }
            ]
        },
        {
            name: "Style",
            isAbstract: true,
            properties: [
                {
                    name: "id",
                    type: "String",
                    isAttr: true,
                    isId: true
                }
            ]
        },
        {
            name: "Extension",
            properties: [
                {
                    name: "values",
                    type: "Element",
                    isMany: true
                }
            ]
        }
    ];
    var associations$3 = [
    ];
    var prefix$4 = "di";
    var xml$1 = {
        tagAlias: "lowerCase"
    };
    var DiPackage = {
        name: name$3,
        uri: uri$3,
        types: types$4,
        associations: associations$3,
        prefix: prefix$4,
        xml: xml$1
    };

    var name$4 = "bpmn.io colors for BPMN";
    var uri$4 = "http://bpmn.io/schema/bpmn/biocolor/1.0";
    var prefix$5 = "bioc";
    var types$5 = [
        {
            name: "ColoredShape",
            "extends": [
                "bpmndi:BPMNShape"
            ],
            properties: [
                {
                    name: "stroke",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "fill",
                    isAttr: true,
                    type: "String"
                }
            ]
        },
        {
            name: "ColoredEdge",
            "extends": [
                "bpmndi:BPMNEdge"
            ],
            properties: [
                {
                    name: "stroke",
                    isAttr: true,
                    type: "String"
                },
                {
                    name: "fill",
                    isAttr: true,
                    type: "String"
                }
            ]
        }
    ];
    var enumerations$2 = [
    ];
    var associations$4 = [
    ];
    var BiocPackage = {
        name: name$4,
        uri: uri$4,
        prefix: prefix$5,
        types: types$5,
        enumerations: enumerations$2,
        associations: associations$4
    };

    var packages = {
        bpmn: BpmnPackage,
        bpmndi: BpmnDiPackage,
        dc: DcPackage,
        di: DiPackage,
        bioc: BiocPackage
    };

    function BpmnModdle$1(additionalPackages, options) {
        var pks = assign({}, packages, additionalPackages);

        return new BpmnModdle(pks, options);
    }

    function elementToString(e) {
        if (!e) {
            return '<null>';
        }

        return '<' + e.$type + (e.id ? ' id="' + e.id : '') + '" />';
    }

    var diRefs = new objectRefs(
        { name: 'bpmnElement', enumerable: true },
        { name: 'di', configurable: true }
    );

    /**
     * Returns true if an element has the given meta-model type
     *
     * @param  {ModdleElement}  element
     * @param  {String}         type
     *
     * @return {Boolean}
     */
    function is(element, type) {
        return element.$instanceOf(type);
    }


    /**
     * Find a suitable display candidate for definitions where the DI does not
     * correctly specify one.
     */
    function findDisplayCandidate(definitions) {
        return find(definitions.rootElements, function(e) {
            return is(e, 'bpmn:Process') || is(e, 'bpmn:Collaboration');
        });
    }


    function BpmnTreeWalker(handler, translate) {

        // list of containers already walked
        var handledElements = {};

        // list of elements to handle deferred to ensure
        // prerequisites are drawn
        var deferred = [];

        // Helpers //////////////////////

        function contextual(fn, ctx) {
            return function(e) {
                fn(e, ctx);
            };
        }

        function handled(element) {
            handledElements[element.id] = element;
        }

        function isHandled(element) {
            return handledElements[element.id];
        }

        function visit(element, ctx) {

            var gfx = element.gfx;

            // avoid multiple rendering of elements
            if (gfx) {
                throw new Error(
                    translate('already rendered {element}', { element: elementToString(element) })
                );
            }

            // call handler
            return handler.element(element, ctx);
        }

        function visitRoot(element, diagram) {
            return handler.root(element, diagram);
        }

        function visitIfDi(element, ctx) {

            try {
                var gfx = element.di && visit(element, ctx);

                handled(element);

                return gfx;
            } catch (e) {
                logError(e.message, { element: element, error: e });

                console.error(translate('failed to import {element}', { element: elementToString(element) }));
                console.error(e);
            }
        }

        function logError(message, context) {
            handler.error(message, context);
        }

        // DI handling //////////////////////

        function registerDi(di) {
            var bpmnElement = di.bpmnElement;

            if (bpmnElement) {
                if (bpmnElement.di) {
                    logError(
                        translate('multiple DI elements defined for {element}', {
                            element: elementToString(bpmnElement)
                        }),
                        { element: bpmnElement }
                    );
                } else {
                    diRefs.bind(bpmnElement, 'di');
                    bpmnElement.di = di;
                }
            } else {
                logError(
                    translate('no bpmnElement referenced in {element}', {
                        element: elementToString(di)
                    }),
                    { element: di }
                );
            }
        }

        function handleDiagram(diagram) {
            handlePlane(diagram.plane);
        }

        function handlePlane(plane) {
            registerDi(plane);

            forEach(plane.planeElement, handlePlaneElement);
        }

        function handlePlaneElement(planeElement) {
            registerDi(planeElement);
        }


        // Semantic handling //////////////////////

        /**
         * Handle definitions and return the rendered diagram (if any)
         *
         * @param {ModdleElement} definitions to walk and import
         * @param {ModdleElement} [diagram] specific diagram to import and display
         *
         * @throws {Error} if no diagram to display could be found
         */
        function handleDefinitions(definitions, diagram) {
            // make sure we walk the correct bpmnElement

            var diagrams = definitions.diagrams;

            if (diagram && diagrams.indexOf(diagram) === -1) {
                throw new Error(translate('diagram not part of bpmn:Definitions'));
            }

            if (!diagram && diagrams && diagrams.length) {
                diagram = diagrams[0];
            }

            // no diagram -> nothing to import
            if (!diagram) {
                throw new Error(translate('no diagram to display'));
            }

            // load DI from selected diagram only
            handleDiagram(diagram);


            var plane = diagram.plane;

            if (!plane) {
                throw new Error(translate(
                    'no plane for {element}',
                    { element: elementToString(diagram) }
                ));
            }

            var rootElement = plane.bpmnElement;

            // ensure we default to a suitable display candidate (process or collaboration),
            // even if non is specified in DI
            if (!rootElement) {
                rootElement = findDisplayCandidate(definitions);

                if (!rootElement) {
                    throw new Error(translate('no process or collaboration to display'));
                } else {

                    logError(
                        translate('correcting missing bpmnElement on {plane} to {rootElement}', {
                            plane: elementToString(plane),
                            rootElement: elementToString(rootElement)
                        })
                    );

                    // correct DI on the fly
                    plane.bpmnElement = rootElement;
                    registerDi(plane);
                }
            }


            var ctx = visitRoot(rootElement, plane);

            if (is(rootElement, 'bpmn:Process')) {
                handleProcess(rootElement, ctx);
            } else if (is(rootElement, 'bpmn:Collaboration')) {
                handleCollaboration(rootElement);

                // force drawing of everything not yet drawn that is part of the target DI
                handleUnhandledProcesses(definitions.rootElements, ctx);
            } else {
                throw new Error(
                    translate('unsupported bpmnElement for {plane}: {rootElement}', {
                        plane: elementToString(plane),
                        rootElement: elementToString(rootElement)
                    })
                );
            }

            // handle all deferred elements
            handleDeferred();
        }

        function handleDeferred() {

            var fn;

            // drain deferred until empty
            while (deferred.length) {
                fn = deferred.shift();

                fn();
            }
        }

        function handleProcess(process, context) {
            handleFlowElementsContainer(process, context);
            handleIoSpecification(process.ioSpecification, context);

            handleArtifacts(process.artifacts, context);

            // log process handled
            handled(process);
        }

        function handleUnhandledProcesses(rootElements, ctx) {

            // walk through all processes that have not yet been drawn and draw them
            // if they contain lanes with DI information.
            // we do this to pass the free-floating lane test cases in the MIWG test suite
            var processes = filter(rootElements, function(e) {
                return !isHandled(e) && is(e, 'bpmn:Process') && e.laneSets;
            });

            processes.forEach(contextual(handleProcess, ctx));
        }

        function handleMessageFlow(messageFlow, context) {
            visitIfDi(messageFlow, context);
        }

        function handleMessageFlows(messageFlows, context) {
            forEach(messageFlows, contextual(handleMessageFlow, context));
        }

        function handleDataAssociation(association, context) {
            visitIfDi(association, context);
        }

        function handleDataInput(dataInput, context) {
            visitIfDi(dataInput, context);
        }

        function handleDataOutput(dataOutput, context) {
            visitIfDi(dataOutput, context);
        }

        function handleArtifact(artifact, context) {

            // bpmn:TextAnnotation
            // bpmn:Group
            // bpmn:Association

            visitIfDi(artifact, context);
        }

        function handleArtifacts(artifacts, context) {

            forEach(artifacts, function(e) {
                if (is(e, 'bpmn:Association')) {
                    deferred.push(function() {
                        handleArtifact(e, context);
                    });
                } else {
                    handleArtifact(e, context);
                }
            });
        }

        function handleIoSpecification(ioSpecification, context) {

            if (!ioSpecification) {
                return;
            }

            forEach(ioSpecification.dataInputs, contextual(handleDataInput, context));
            forEach(ioSpecification.dataOutputs, contextual(handleDataOutput, context));
        }

        function handleSubProcess(subProcess, context) {
            handleFlowElementsContainer(subProcess, context);
            handleArtifacts(subProcess.artifacts, context);
        }

        function handleFlowNode(flowNode, context) {
            var childCtx = visitIfDi(flowNode, context);

            if (is(flowNode, 'bpmn:SubProcess')) {
                handleSubProcess(flowNode, childCtx || context);
            }

            if (is(flowNode, 'bpmn:Activity')) {
                handleIoSpecification(flowNode.ioSpecification, context);
            }

            // defer handling of associations
            // affected types:
            //
            //   * bpmn:Activity
            //   * bpmn:ThrowEvent
            //   * bpmn:CatchEvent
            //
            deferred.push(function() {
                forEach(flowNode.dataInputAssociations, contextual(handleDataAssociation, context));
                forEach(flowNode.dataOutputAssociations, contextual(handleDataAssociation, context));
            });
        }

        function handleSequenceFlow(sequenceFlow, context) {
            visitIfDi(sequenceFlow, context);
        }

        function handleDataElement(dataObject, context) {
            visitIfDi(dataObject, context);
        }

        function handleBoundaryEvent(dataObject, context) {
            visitIfDi(dataObject, context);
        }

        function handleLane(lane, context) {

            deferred.push(function() {

                var newContext = visitIfDi(lane, context);

                if (lane.childLaneSet) {
                    handleLaneSet(lane.childLaneSet, newContext || context);
                }

                wireFlowNodeRefs(lane);
            });
        }

        function handleLaneSet(laneSet, context) {
            forEach(laneSet.lanes, contextual(handleLane, context));
        }

        function handleLaneSets(laneSets, context) {
            forEach(laneSets, contextual(handleLaneSet, context));
        }

        function handleFlowElementsContainer(container, context) {
            handleFlowElements(container.flowElements, context);

            if (container.laneSets) {
                handleLaneSets(container.laneSets, context);
            }
        }

        function handleFlowElements(flowElements, context) {
            forEach(flowElements, function(e) {
                if (is(e, 'bpmn:SequenceFlow')) {
                    deferred.push(function() {
                        handleSequenceFlow(e, context);
                    });
                } else if (is(e, 'bpmn:BoundaryEvent')) {
                    deferred.unshift(function() {
                        handleBoundaryEvent(e, context);
                    });
                } else if (is(e, 'bpmn:FlowNode')) {
                    handleFlowNode(e, context);
                } else if (is(e, 'bpmn:DataObject')) ; else if (is(e, 'bpmn:DataStoreReference')) {
                    handleDataElement(e, context);
                } else if (is(e, 'bpmn:DataObjectReference')) {
                    handleDataElement(e, context);
                } else {
                    logError(
                        translate('unrecognized flowElement {element} in context {context}', {
                            element: elementToString(e),
                            context: (context ? elementToString(context.businessObject) : 'null')
                        }),
                        { element: e, context: context }
                    );
                }
            });
        }

        function handleParticipant(participant, context) {
            var newCtx = visitIfDi(participant, context);

            var process = participant.processRef;
            if (process) {
                handleProcess(process, newCtx || context);
            }
        }

        function handleCollaboration(collaboration) {

            forEach(collaboration.participants, contextual(handleParticipant));

            handleArtifacts(collaboration.artifacts);

            // handle message flows latest in the process
            deferred.push(function() {
                handleMessageFlows(collaboration.messageFlows);
            });
        }


        function wireFlowNodeRefs(lane) {
            // wire the virtual flowNodeRefs <-> relationship
            forEach(lane.flowNodeRef, function(flowNode) {
                var lanes = flowNode.get('lanes');

                if (lanes) {
                    lanes.push(lane);
                }
            });
        }

        // API //////////////////////

        return {
            handleDeferred: handleDeferred,
            handleDefinitions: handleDefinitions,
            handleSubProcess: handleSubProcess,
            registerDi: registerDi
        };
    }

    /**
     * Import the definitions into a diagram.
     *
     * Errors and warnings are reported through the specified callback.
     *
     * @param  {djs.Diagram} diagram
     * @param  {ModdleElement<Definitions>} definitions
     * @param  {ModdleElement<BPMNDiagram>} [bpmnDiagram] the diagram to be rendered
     * (if not provided, the first one will be rendered)
     * @param  {Function} done the callback, invoked with (err, [ warning ]) once the import is done
     */
    function importBpmnDiagram(diagram, definitions, bpmnDiagram, done) {

        if (isFunction(bpmnDiagram)) {
            done = bpmnDiagram;
            bpmnDiagram = null;
        }

        var importer,
            eventBus,
            translate;

        var error,
            warnings = [];

        /**
         * Walk the diagram semantically, importing (=drawing)
         * all elements you encounter.
         *
         * @param {ModdleElement<Definitions>} definitions
         * @param {ModdleElement<BPMNDiagram>} bpmnDiagram
         */
        function render(definitions, bpmnDiagram) {

            var visitor = {

                root: function(element) {
                    return importer.add(element);
                },

                element: function(element, parentShape) {
                    return importer.add(element, parentShape);
                },

                error: function(message, context) {
                    warnings.push({ message: message, context: context });
                }
            };

            var walker = new BpmnTreeWalker(visitor, translate);

            // traverse BPMN 2.0 document model,
            // starting at definitions
            walker.handleDefinitions(definitions, bpmnDiagram);
        }

        try {
            importer = diagram.get('bpmnImporter');
            eventBus = diagram.get('eventBus');
            translate = diagram.get('translate');

            eventBus.fire('import.render.start', { definitions: definitions });

            render(definitions, bpmnDiagram);

            eventBus.fire('import.render.complete', {
                error: error,
                warnings: warnings
            });
        } catch (e) {
            error = e;
        }

        done(error, warnings);
    }

    /**
     * Is an element of the given BPMN type?
     *
     * @param  {djs.model.Base|ModdleElement} element
     * @param  {String} type
     *
     * @return {Boolean}
     */
    function is$1(element, type) {
        var bo = getBusinessObject(element);

        return bo && (typeof bo.$instanceOf === 'function') && bo.$instanceOf(type);
    }


    /**
     * Return the business object for a given element.
     *
     * @param  {djs.model.Base|ModdleElement} element
     *
     * @return {ModdleElement}
     */
    function getBusinessObject(element) {
        return (element && element.businessObject) || element;
    }

    function isExpanded(element) {

        if (is$1(element, 'bpmn:CallActivity')) {
            return false;
        }

        if (is$1(element, 'bpmn:SubProcess')) {
            return !!getBusinessObject(element).di.isExpanded;
        }

        if (is$1(element, 'bpmn:Participant')) {
            return !!getBusinessObject(element).processRef;
        }

        return true;
    }

    function isEventSubProcess(element) {
        return element && !!getBusinessObject(element).triggeredByEvent;
    }

    function getLabelAttr(semantic) {
        if (
            is$1(semantic, 'bpmn:FlowElement') ||
            is$1(semantic, 'bpmn:Participant') ||
            is$1(semantic, 'bpmn:Lane') ||
            is$1(semantic, 'bpmn:SequenceFlow') ||
            is$1(semantic, 'bpmn:MessageFlow') ||
            is$1(semantic, 'bpmn:DataInput') ||
            is$1(semantic, 'bpmn:DataOutput')
        ) {
            return 'name';
        }

        if (is$1(semantic, 'bpmn:TextAnnotation')) {
            return 'text';
        }

        if (is$1(semantic, 'bpmn:Group')) {
            return 'categoryValueRef';
        }
    }

    function getCategoryValue(semantic) {
        var categoryValueRef = semantic['categoryValueRef'];

        if (!categoryValueRef) {
            return '';
        }


        return categoryValueRef.value || '';
    }

    function getLabel(element) {
        var semantic = element.businessObject,
            attr = getLabelAttr(semantic);

        if (attr) {

            if (attr === 'categoryValueRef') {

                return getCategoryValue(semantic);
            }

            return semantic[attr] || '';
        }
    }

    // element utils //////////////////////

    /**
     * Checks if eventDefinition of the given element matches with semantic type.
     *
     * @return {boolean} true if element is of the given semantic type
     */
    function isTypedEvent(event, eventDefinitionType, filter) {

        function matches(definition, filter) {
            return every(filter, function(val, key) {

                // we want a == conversion here, to be able to catch
                // undefined == false and friends
                /* jshint -W116 */
                return definition[key] == val;
            });
        }

        return some(event.eventDefinitions, function(definition) {
            return definition.$type === eventDefinitionType && matches(event, filter);
        });
    }

    function isThrowEvent(event) {
        return (event.$type === 'bpmn:IntermediateThrowEvent') || (event.$type === 'bpmn:EndEvent');
    }

    function isCollection(element) {
        var dataObject = element.dataObjectRef;

        return element.isCollection || (dataObject && dataObject.isCollection);
    }

    function getDi(element) {
        return element.businessObject.di;
    }

    function getSemantic(element) {
        return element.businessObject;
    }


    // color access //////////////////////

    function getFillColor(element, defaultColor) {
        return getDi(element).get('bioc:fill') || defaultColor || 'white';
    }

    function getStrokeColor(element, defaultColor) {
        return getDi(element).get('bioc:stroke') || defaultColor || 'black';
    }


    // cropping path customizations //////////////////////

    function getCirclePath(shape) {

        var cx = shape.x + shape.width / 2,
            cy = shape.y + shape.height / 2,
            radius = shape.width / 2;

        var circlePath = [
            ['M', cx, cy],
            ['m', 0, -radius],
            ['a', radius, radius, 0, 1, 1, 0, 2 * radius],
            ['a', radius, radius, 0, 1, 1, 0, -2 * radius],
            ['z']
        ];

        return componentsToPath(circlePath);
    }

    function getRoundRectPath(shape, borderRadius) {

        var x = shape.x,
            y = shape.y,
            width = shape.width,
            height = shape.height;

        var roundRectPath = [
            ['M', x + borderRadius, y],
            ['l', width - borderRadius * 2, 0],
            ['a', borderRadius, borderRadius, 0, 0, 1, borderRadius, borderRadius],
            ['l', 0, height - borderRadius * 2],
            ['a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, borderRadius],
            ['l', borderRadius * 2 - width, 0],
            ['a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, -borderRadius],
            ['l', 0, borderRadius * 2 - height],
            ['a', borderRadius, borderRadius, 0, 0, 1, borderRadius, -borderRadius],
            ['z']
        ];

        return componentsToPath(roundRectPath);
    }

    function getDiamondPath(shape) {

        var width = shape.width,
            height = shape.height,
            x = shape.x,
            y = shape.y,
            halfWidth = width / 2,
            halfHeight = height / 2;

        var diamondPath = [
            ['M', x + halfWidth, y],
            ['l', halfWidth, halfHeight],
            ['l', -halfWidth, halfHeight],
            ['l', -halfWidth, -halfHeight],
            ['z']
        ];

        return componentsToPath(diamondPath);
    }

    function getRectPath(shape) {
        var x = shape.x,
            y = shape.y,
            width = shape.width,
            height = shape.height;

        var rectPath = [
            ['M', x, y],
            ['l', width, 0],
            ['l', 0, height],
            ['l', -width, 0],
            ['z']
        ];

        return componentsToPath(rectPath);
    }

    function createCommonjsModule$1(fn, module) {
        return module = { exports: {} }, fn(module, module.exports), module.exports;
    }

    var hat_1 = createCommonjsModule$1(function (module) {
        var hat = module.exports = function (bits, base) {
            if (!base) base = 16;
            if (bits === undefined) bits = 128;
            if (bits <= 0) return '0';

            var digits = Math.log(Math.pow(2, bits)) / Math.log(base);
            for (var i = 2; digits === Infinity; i *= 2) {
                digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i;
            }

            var rem = digits - Math.floor(digits);

            var res = '';

            for (var i = 0; i < Math.floor(digits); i++) {
                var x = Math.floor(Math.random() * base).toString(base);
                res = x + res;
            }

            if (rem) {
                var b = Math.pow(base, rem);
                var x = Math.floor(Math.random() * b).toString(base);
                res = x + res;
            }

            var parsed = parseInt(res, base);
            if (parsed !== Infinity && parsed >= Math.pow(2, bits)) {
                return hat(bits, base)
            }
            else return res;
        };

        hat.rack = function (bits, base, expandBy) {
            var fn = function (data) {
                var iters = 0;
                do {
                    if (iters ++ > 10) {
                        if (expandBy) bits += expandBy;
                        else throw new Error('too many ID collisions, use more bits')
                    }

                    var id = hat(bits, base);
                } while (Object.hasOwnProperty.call(hats, id));

                hats[id] = data;
                return id;
            };
            var hats = fn.hats = {};

            fn.get = function (id) {
                return fn.hats[id];
            };

            fn.set = function (id, value) {
                fn.hats[id] = value;
                return fn;
            };

            fn.bits = bits || 128;
            fn.base = base || 16;
            return fn;
        };
    });

    /**
     * Create a new id generator / cache instance.
     *
     * You may optionally provide a seed that is used internally.
     *
     * @param {Seed} seed
     */

    function Ids(seed) {
        if (!(this instanceof Ids)) {
            return new Ids(seed);
        }

        seed = seed || [128, 36, 1];
        this._seed = seed.length ? hat_1.rack(seed[0], seed[1], seed[2]) : seed;
    }
    /**
     * Generate a next id.
     *
     * @param {Object} [element] element to bind the id to
     *
     * @return {String} id
     */

    Ids.prototype.next = function (element) {
        return this._seed(element || true);
    };
    /**
     * Generate a next id with a given prefix.
     *
     * @param {Object} [element] element to bind the id to
     *
     * @return {String} id
     */


    Ids.prototype.nextPrefixed = function (prefix, element) {
        var id;

        do {
            id = prefix + this.next(true);
        } while (this.assigned(id)); // claim {prefix}{random}


        this.claim(id, element); // return

        return id;
    };
    /**
     * Manually claim an existing id.
     *
     * @param {String} id
     * @param {String} [element] element the id is claimed by
     */


    Ids.prototype.claim = function (id, element) {
        this._seed.set(id, element || true);
    };
    /**
     * Returns true if the given id has already been assigned.
     *
     * @param  {String} id
     * @return {Boolean}
     */


    Ids.prototype.assigned = function (id) {
        return this._seed.get(id) || false;
    };
    /**
     * Unclaim an id.
     *
     * @param  {String} id the id to unclaim
     */


    Ids.prototype.unclaim = function (id) {
        delete this._seed.hats[id];
    };
    /**
     * Clear all claimed ids.
     */


    Ids.prototype.clear = function () {
        var hats = this._seed.hats,
            id;

        for (id in hats) {
            this.unclaim(id);
        }
    };

    var RENDERER_IDS = new Ids();

    var TASK_BORDER_RADIUS = 10;
    var INNER_OUTER_DIST = 3;

    var DEFAULT_FILL_OPACITY = .95,
        HIGH_FILL_OPACITY = .35;


    function BpmnRenderer(
        config, eventBus, styles, pathMap,
        canvas, textRenderer, priority) {

        BaseRenderer.call(this, eventBus, priority);

        var defaultFillColor = config && config.defaultFillColor,
            defaultStrokeColor = config && config.defaultStrokeColor;

        var rendererId = RENDERER_IDS.next();

        var markers = {};

        var computeStyle = styles.computeStyle;

        function addMarker(id, options) {
            var attrs = assign({
                fill: 'black',
                strokeWidth: 1,
                strokeLinecap: 'round',
                strokeDasharray: 'none'
            }, options.attrs);

            var ref = options.ref || { x: 0, y: 0 };

            var scale = options.scale || 1;

            // fix for safari / chrome / firefox bug not correctly
            // resetting stroke dash array
            if (attrs.strokeDasharray === 'none') {
                attrs.strokeDasharray = [10000, 1];
            }

            var marker = create('marker');

            attr$1(options.element, attrs);

            append(marker, options.element);

            attr$1(marker, {
                id: id,
                viewBox: '0 0 20 20',
                refX: ref.x,
                refY: ref.y,
                markerWidth: 20 * scale,
                markerHeight: 20 * scale,
                orient: 'auto'
            });

            var defs = query('defs', canvas._svg);

            if (!defs) {
                defs = create('defs');

                append(canvas._svg, defs);
            }

            append(defs, marker);

            markers[id] = marker;
        }

        function colorEscape(str) {
            return str.replace(/[()\s,#]+/g, '_');
        }

        function marker(type, fill, stroke) {
            var id = type + '-' + colorEscape(fill) + '-' + colorEscape(stroke) + '-' + rendererId;

            if (!markers[id]) {
                createMarker(id, type, fill, stroke);
            }

            return 'url(#' + id + ')';
        }

        function createMarker(id, type, fill, stroke) {

            if (type === 'sequenceflow-end') {
                var sequenceflowEnd = create('path');
                attr$1(sequenceflowEnd, { d: 'M 1 5 L 11 10 L 1 15 Z' });

                addMarker(id, {
                    element: sequenceflowEnd,
                    ref: { x: 11, y: 10 },
                    scale: 0.5,
                    attrs: {
                        fill: stroke,
                        stroke: stroke
                    }
                });
            }

            if (type === 'messageflow-start') {
                var messageflowStart = create('circle');
                attr$1(messageflowStart, { cx: 6, cy: 6, r: 3.5 });

                addMarker(id, {
                    element: messageflowStart,
                    attrs: {
                        fill: fill,
                        stroke: stroke
                    },
                    ref: { x: 6, y: 6 }
                });
            }

            if (type === 'messageflow-end') {
                var messageflowEnd = create('path');
                attr$1(messageflowEnd, { d: 'm 1 5 l 0 -3 l 7 3 l -7 3 z' });

                addMarker(id, {
                    element: messageflowEnd,
                    attrs: {
                        fill: fill,
                        stroke: stroke,
                        strokeLinecap: 'butt'
                    },
                    ref: { x: 8.5, y: 5 }
                });
            }

            if (type === 'association-start') {
                var associationStart = create('path');
                attr$1(associationStart, { d: 'M 11 5 L 1 10 L 11 15' });

                addMarker(id, {
                    element: associationStart,
                    attrs: {
                        fill: 'none',
                        stroke: stroke,
                        strokeWidth: 1.5
                    },
                    ref: { x: 1, y: 10 },
                    scale: 0.5
                });
            }

            if (type === 'association-end') {
                var associationEnd = create('path');
                attr$1(associationEnd, { d: 'M 1 5 L 11 10 L 1 15' });

                addMarker(id, {
                    element: associationEnd,
                    attrs: {
                        fill: 'none',
                        stroke: stroke,
                        strokeWidth: 1.5
                    },
                    ref: { x: 12, y: 10 },
                    scale: 0.5
                });
            }

            if (type === 'conditional-flow-marker') {
                var conditionalflowMarker = create('path');
                attr$1(conditionalflowMarker, { d: 'M 0 10 L 8 6 L 16 10 L 8 14 Z' });

                addMarker(id, {
                    element: conditionalflowMarker,
                    attrs: {
                        fill: fill,
                        stroke: stroke
                    },
                    ref: { x: -1, y: 10 },
                    scale: 0.5
                });
            }

            if (type === 'conditional-default-flow-marker') {
                var conditionaldefaultflowMarker = create('path');
                attr$1(conditionaldefaultflowMarker, { d: 'M 6 4 L 10 16' });

                addMarker(id, {
                    element: conditionaldefaultflowMarker,
                    attrs: {
                        stroke: stroke
                    },
                    ref: { x: 0, y: 10 },
                    scale: 0.5
                });
            }
        }

        function drawCircle(parentGfx, width, height, offset, attrs) {

            if (isObject(offset)) {
                attrs = offset;
                offset = 0;
            }

            offset = offset || 0;

            attrs = computeStyle(attrs, {
                stroke: 'black',
                strokeWidth: 2,
                fill: 'white'
            });

            if (attrs.fill === 'none') {
                delete attrs.fillOpacity;
            }

            var cx = width / 2,
                cy = height / 2;

            var circle = create('circle');
            attr$1(circle, {
                cx: cx,
                cy: cy,
                r: Math.round((width + height) / 4 - offset)
            });
            attr$1(circle, attrs);

            append(parentGfx, circle);

            return circle;
        }

        function drawRect(parentGfx, width, height, r, offset, attrs) {

            if (isObject(offset)) {
                attrs = offset;
                offset = 0;
            }

            offset = offset || 0;

            attrs = computeStyle(attrs, {
                stroke: 'black',
                strokeWidth: 2,
                fill: 'white'
            });

            var rect = create('rect');
            attr$1(rect, {
                x: offset,
                y: offset,
                width: width - offset * 2,
                height: height - offset * 2,
                rx: r,
                ry: r
            });
            attr$1(rect, attrs);

            append(parentGfx, rect);

            return rect;
        }

        function drawDiamond(parentGfx, width, height, attrs) {

            var x_2 = width / 2;
            var y_2 = height / 2;

            var points = [{ x: x_2, y: 0 }, { x: width, y: y_2 }, { x: x_2, y: height }, { x: 0, y: y_2 }];

            var pointsString = points.map(function(point) {
                return point.x + ',' + point.y;
            }).join(' ');

            attrs = computeStyle(attrs, {
                stroke: 'black',
                strokeWidth: 2,
                fill: 'white'
            });

            var polygon = create('polygon');
            attr$1(polygon, {
                points: pointsString
            });
            attr$1(polygon, attrs);

            append(parentGfx, polygon);

            return polygon;
        }

        function drawLine(parentGfx, waypoints, attrs) {
            attrs = computeStyle(attrs, [ 'no-fill' ], {
                stroke: 'black',
                strokeWidth: 2,
                fill: 'none'
            });

            var line = createLine(waypoints, attrs);

            append(parentGfx, line);

            return line;
        }

        function drawPath(parentGfx, d, attrs) {

            attrs = computeStyle(attrs, [ 'no-fill' ], {
                strokeWidth: 2,
                stroke: 'black'
            });

            var path = create('path');
            attr$1(path, { d: d });
            attr$1(path, attrs);

            append(parentGfx, path);

            return path;
        }

        function drawMarker(type, parentGfx, path, attrs) {
            return drawPath(parentGfx, path, assign({ 'data-marker': type }, attrs));
        }

        function as(type) {
            return function(parentGfx, element) {
                return handlers[type](parentGfx, element);
            };
        }

        function renderer(type) {
            return handlers[type];
        }

        function renderEventContent(element, parentGfx) {

            var event = getSemantic(element);
            var isThrowing = isThrowEvent(event);

            if (event.eventDefinitions && event.eventDefinitions.length>1) {
                if (event.parallelMultiple) {
                    return renderer('bpmn:ParallelMultipleEventDefinition')(parentGfx, element, isThrowing);
                }
                else {
                    return renderer('bpmn:MultipleEventDefinition')(parentGfx, element, isThrowing);
                }
            }

            if (isTypedEvent(event, 'bpmn:MessageEventDefinition')) {
                return renderer('bpmn:MessageEventDefinition')(parentGfx, element, isThrowing);
            }

            if (isTypedEvent(event, 'bpmn:TimerEventDefinition')) {
                return renderer('bpmn:TimerEventDefinition')(parentGfx, element, isThrowing);
            }

            if (isTypedEvent(event, 'bpmn:ConditionalEventDefinition')) {
                return renderer('bpmn:ConditionalEventDefinition')(parentGfx, element);
            }

            if (isTypedEvent(event, 'bpmn:SignalEventDefinition')) {
                return renderer('bpmn:SignalEventDefinition')(parentGfx, element, isThrowing);
            }

            if (isTypedEvent(event, 'bpmn:EscalationEventDefinition')) {
                return renderer('bpmn:EscalationEventDefinition')(parentGfx, element, isThrowing);
            }

            if (isTypedEvent(event, 'bpmn:LinkEventDefinition')) {
                return renderer('bpmn:LinkEventDefinition')(parentGfx, element, isThrowing);
            }

            if (isTypedEvent(event, 'bpmn:ErrorEventDefinition')) {
                return renderer('bpmn:ErrorEventDefinition')(parentGfx, element, isThrowing);
            }

            if (isTypedEvent(event, 'bpmn:CancelEventDefinition')) {
                return renderer('bpmn:CancelEventDefinition')(parentGfx, element, isThrowing);
            }

            if (isTypedEvent(event, 'bpmn:CompensateEventDefinition')) {
                return renderer('bpmn:CompensateEventDefinition')(parentGfx, element, isThrowing);
            }

            if (isTypedEvent(event, 'bpmn:TerminateEventDefinition')) {
                return renderer('bpmn:TerminateEventDefinition')(parentGfx, element, isThrowing);
            }

            return null;
        }

        function renderLabel(parentGfx, label, options) {

            options = assign({
                size: {
                    width: 100
                }
            }, options);

            var text = textRenderer.createText(label || '', options);

            classes$1(text).add('djs-label');

            append(parentGfx, text);

            return text;
        }

        function renderEmbeddedLabel(parentGfx, element, align) {
            var semantic = getSemantic(element);

            return renderLabel(parentGfx, semantic.name, {
                box: element,
                align: align,
                padding: 5,
                style: {
                    fill: getStrokeColor(element, defaultStrokeColor)
                }
            });
        }

        function renderExternalLabel(parentGfx, element) {

            var box = {
                width: 90,
                height: 30,
                x: element.width / 2 + element.x,
                y: element.height / 2 + element.y
            };

            return renderLabel(parentGfx, getLabel(element), {
                box: box,
                fitBox: true,
                style: assign(
                    {},
                    textRenderer.getExternalStyle(),
                    {
                        fill: getStrokeColor(element, defaultStrokeColor)
                    }
                )
            });
        }

        function renderLaneLabel(parentGfx, text, element) {
            var textBox = renderLabel(parentGfx, text, {
                box: {
                    height: 30,
                    width: element.height
                },
                align: 'center-middle',
                style: {
                    fill: getStrokeColor(element, defaultStrokeColor)
                }
            });

            var top = -1 * element.height;

            transform$1(textBox, 0, -top, 270);
        }

        function createPathFromConnection(connection) {
            var waypoints = connection.waypoints;

            var pathData = 'm  ' + waypoints[0].x + ',' + waypoints[0].y;
            for (var i = 1; i < waypoints.length; i++) {
                pathData += 'L' + waypoints[i].x + ',' + waypoints[i].y + ' ';
            }
            return pathData;
        }

        var handlers = this.handlers = {
            'bpmn:Event': function(parentGfx, element, attrs) {

                if (!('fillOpacity' in attrs)) {
                    attrs.fillOpacity = DEFAULT_FILL_OPACITY;
                }

                return drawCircle(parentGfx, element.width, element.height, attrs);
            },
            'bpmn:StartEvent': function(parentGfx, element) {
                var attrs = {
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                };

                var semantic = getSemantic(element);

                if (!semantic.isInterrupting) {
                    attrs = {
                        strokeDasharray: '6',
                        strokeLinecap: 'round',
                        fill: getFillColor(element, defaultFillColor),
                        stroke: getStrokeColor(element, defaultStrokeColor)
                    };
                }

                var circle = renderer('bpmn:Event')(parentGfx, element, attrs);

                renderEventContent(element, parentGfx);

                return circle;
            },
            'bpmn:MessageEventDefinition': function(parentGfx, element, isThrowing) {
                var pathData = pathMap.getScaledPath('EVENT_MESSAGE', {
                    xScaleFactor: 0.9,
                    yScaleFactor: 0.9,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: 0.235,
                        my: 0.315
                    }
                });

                var fill = isThrowing ? getStrokeColor(element, defaultStrokeColor) : getFillColor(element, defaultFillColor);
                var stroke = isThrowing ? getFillColor(element, defaultFillColor) : getStrokeColor(element, defaultStrokeColor);

                var messagePath = drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: fill,
                    stroke: stroke
                });

                return messagePath;
            },
            'bpmn:TimerEventDefinition': function(parentGfx, element) {
                var circle = drawCircle(parentGfx, element.width, element.height, 0.2 * element.height, {
                    strokeWidth: 2,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                var pathData = pathMap.getScaledPath('EVENT_TIMER_WH', {
                    xScaleFactor: 0.75,
                    yScaleFactor: 0.75,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: 0.5,
                        my: 0.5
                    }
                });

                drawPath(parentGfx, pathData, {
                    strokeWidth: 2,
                    strokeLinecap: 'square',
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                for (var i = 0;i < 12; i++) {

                    var linePathData = pathMap.getScaledPath('EVENT_TIMER_LINE', {
                        xScaleFactor: 0.75,
                        yScaleFactor: 0.75,
                        containerWidth: element.width,
                        containerHeight: element.height,
                        position: {
                            mx: 0.5,
                            my: 0.5
                        }
                    });

                    var width = element.width / 2;
                    var height = element.height / 2;

                    drawPath(parentGfx, linePathData, {
                        strokeWidth: 1,
                        strokeLinecap: 'square',
                        transform: 'rotate(' + (i * 30) + ',' + height + ',' + width + ')',
                        stroke: getStrokeColor(element, defaultStrokeColor)
                    });
                }

                return circle;
            },
            'bpmn:EscalationEventDefinition': function(parentGfx, event, isThrowing) {
                var pathData = pathMap.getScaledPath('EVENT_ESCALATION', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: event.width,
                    containerHeight: event.height,
                    position: {
                        mx: 0.5,
                        my: 0.2
                    }
                });

                var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';

                return drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: fill,
                    stroke: getStrokeColor(event, defaultStrokeColor)
                });
            },
            'bpmn:ConditionalEventDefinition': function(parentGfx, event) {
                var pathData = pathMap.getScaledPath('EVENT_CONDITIONAL', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: event.width,
                    containerHeight: event.height,
                    position: {
                        mx: 0.5,
                        my: 0.222
                    }
                });

                return drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    stroke: getStrokeColor(event, defaultStrokeColor)
                });
            },
            'bpmn:LinkEventDefinition': function(parentGfx, event, isThrowing) {
                var pathData = pathMap.getScaledPath('EVENT_LINK', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: event.width,
                    containerHeight: event.height,
                    position: {
                        mx: 0.57,
                        my: 0.263
                    }
                });

                var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';

                return drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: fill,
                    stroke: getStrokeColor(event, defaultStrokeColor)
                });
            },
            'bpmn:ErrorEventDefinition': function(parentGfx, event, isThrowing) {
                var pathData = pathMap.getScaledPath('EVENT_ERROR', {
                    xScaleFactor: 1.1,
                    yScaleFactor: 1.1,
                    containerWidth: event.width,
                    containerHeight: event.height,
                    position: {
                        mx: 0.2,
                        my: 0.722
                    }
                });

                var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';

                return drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: fill,
                    stroke: getStrokeColor(event, defaultStrokeColor)
                });
            },
            'bpmn:CancelEventDefinition': function(parentGfx, event, isThrowing) {
                var pathData = pathMap.getScaledPath('EVENT_CANCEL_45', {
                    xScaleFactor: 1.0,
                    yScaleFactor: 1.0,
                    containerWidth: event.width,
                    containerHeight: event.height,
                    position: {
                        mx: 0.638,
                        my: -0.055
                    }
                });

                var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';

                var path = drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: fill,
                    stroke: getStrokeColor(event, defaultStrokeColor)
                });

                rotate(path, 45);

                return path;
            },
            'bpmn:CompensateEventDefinition': function(parentGfx, event, isThrowing) {
                var pathData = pathMap.getScaledPath('EVENT_COMPENSATION', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: event.width,
                    containerHeight: event.height,
                    position: {
                        mx: 0.22,
                        my: 0.5
                    }
                });

                var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';

                return drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: fill,
                    stroke: getStrokeColor(event, defaultStrokeColor)
                });
            },
            'bpmn:SignalEventDefinition': function(parentGfx, event, isThrowing) {
                var pathData = pathMap.getScaledPath('EVENT_SIGNAL', {
                    xScaleFactor: 0.9,
                    yScaleFactor: 0.9,
                    containerWidth: event.width,
                    containerHeight: event.height,
                    position: {
                        mx: 0.5,
                        my: 0.2
                    }
                });

                var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';

                return drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: fill,
                    stroke: getStrokeColor(event, defaultStrokeColor)
                });
            },
            'bpmn:MultipleEventDefinition': function(parentGfx, event, isThrowing) {
                var pathData = pathMap.getScaledPath('EVENT_MULTIPLE', {
                    xScaleFactor: 1.1,
                    yScaleFactor: 1.1,
                    containerWidth: event.width,
                    containerHeight: event.height,
                    position: {
                        mx: 0.222,
                        my: 0.36
                    }
                });

                var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';

                return drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: fill
                });
            },
            'bpmn:ParallelMultipleEventDefinition': function(parentGfx, event) {
                var pathData = pathMap.getScaledPath('EVENT_PARALLEL_MULTIPLE', {
                    xScaleFactor: 1.2,
                    yScaleFactor: 1.2,
                    containerWidth: event.width,
                    containerHeight: event.height,
                    position: {
                        mx: 0.458,
                        my: 0.194
                    }
                });

                return drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: getStrokeColor(event, defaultStrokeColor),
                    stroke: getStrokeColor(event, defaultStrokeColor)
                });
            },
            'bpmn:EndEvent': function(parentGfx, element) {
                var circle = renderer('bpmn:Event')(parentGfx, element, {
                    strokeWidth: 4,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                renderEventContent(element, parentGfx);

                return circle;
            },
            'bpmn:TerminateEventDefinition': function(parentGfx, element) {
                var circle = drawCircle(parentGfx, element.width, element.height, 8, {
                    strokeWidth: 4,
                    fill: getStrokeColor(element, defaultStrokeColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                return circle;
            },
            'bpmn:IntermediateEvent': function(parentGfx, element) {
                var outer = renderer('bpmn:Event')(parentGfx, element, {
                    strokeWidth: 1,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                /* inner */
                drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, {
                    strokeWidth: 1,
                    fill: getFillColor(element, 'none'),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                renderEventContent(element, parentGfx);

                return outer;
            },
            'bpmn:IntermediateCatchEvent': as('bpmn:IntermediateEvent'),
            'bpmn:IntermediateThrowEvent': as('bpmn:IntermediateEvent'),

            'bpmn:Activity': function(parentGfx, element, attrs) {

                attrs = attrs || {};

                if (!('fillOpacity' in attrs)) {
                    attrs.fillOpacity = DEFAULT_FILL_OPACITY;
                }

                return drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, attrs);
            },

            'bpmn:Task': function(parentGfx, element) {
                var attrs = {
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                };

                var rect = renderer('bpmn:Activity')(parentGfx, element, attrs);

                renderEmbeddedLabel(parentGfx, element, 'center-middle');
                attachTaskMarkers(parentGfx, element);

                return rect;
            },
            'bpmn:ServiceTask': function(parentGfx, element) {
                var task = renderer('bpmn:Task')(parentGfx, element);

                var pathDataBG = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
                    abspos: {
                        x: 12,
                        y: 18
                    }
                });

                /* service bg */ drawPath(parentGfx, pathDataBG, {
                    strokeWidth: 1,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                var fillPathData = pathMap.getScaledPath('TASK_TYPE_SERVICE_FILL', {
                    abspos: {
                        x: 17.2,
                        y: 18
                    }
                });

                /* service fill */ drawPath(parentGfx, fillPathData, {
                    strokeWidth: 0,
                    fill: getFillColor(element, defaultFillColor)
                });

                var pathData = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
                    abspos: {
                        x: 17,
                        y: 22
                    }
                });

                /* service */ drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                return task;
            },
            'bpmn:UserTask': function(parentGfx, element) {
                var task = renderer('bpmn:Task')(parentGfx, element);

                var x = 15;
                var y = 12;

                var pathData = pathMap.getScaledPath('TASK_TYPE_USER_1', {
                    abspos: {
                        x: x,
                        y: y
                    }
                });

                /* user path */ drawPath(parentGfx, pathData, {
                    strokeWidth: 0.5,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                var pathData2 = pathMap.getScaledPath('TASK_TYPE_USER_2', {
                    abspos: {
                        x: x,
                        y: y
                    }
                });

                /* user2 path */ drawPath(parentGfx, pathData2, {
                    strokeWidth: 0.5,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                var pathData3 = pathMap.getScaledPath('TASK_TYPE_USER_3', {
                    abspos: {
                        x: x,
                        y: y
                    }
                });

                /* user3 path */ drawPath(parentGfx, pathData3, {
                    strokeWidth: 0.5,
                    fill: getStrokeColor(element, defaultStrokeColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                return task;
            },
            'bpmn:ManualTask': function(parentGfx, element) {
                var task = renderer('bpmn:Task')(parentGfx, element);

                var pathData = pathMap.getScaledPath('TASK_TYPE_MANUAL', {
                    abspos: {
                        x: 17,
                        y: 15
                    }
                });

                /* manual path */ drawPath(parentGfx, pathData, {
                    strokeWidth: 0.5, // 0.25,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                return task;
            },
            'bpmn:SendTask': function(parentGfx, element) {
                var task = renderer('bpmn:Task')(parentGfx, element);

                var pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: 21,
                    containerHeight: 14,
                    position: {
                        mx: 0.285,
                        my: 0.357
                    }
                });

                /* send path */ drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: getStrokeColor(element, defaultStrokeColor),
                    stroke: getFillColor(element, defaultFillColor)
                });

                return task;
            },
            'bpmn:ReceiveTask' : function(parentGfx, element) {
                var semantic = getSemantic(element);

                var task = renderer('bpmn:Task')(parentGfx, element);
                var pathData;

                if (semantic.instantiate) {
                    drawCircle(parentGfx, 28, 28, 20 * 0.22, { strokeWidth: 1 });

                    pathData = pathMap.getScaledPath('TASK_TYPE_INSTANTIATING_SEND', {
                        abspos: {
                            x: 7.77,
                            y: 9.52
                        }
                    });
                } else {

                    pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
                        xScaleFactor: 0.9,
                        yScaleFactor: 0.9,
                        containerWidth: 21,
                        containerHeight: 14,
                        position: {
                            mx: 0.3,
                            my: 0.4
                        }
                    });
                }

                /* receive path */ drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                return task;
            },
            'bpmn:ScriptTask': function(parentGfx, element) {
                var task = renderer('bpmn:Task')(parentGfx, element);

                var pathData = pathMap.getScaledPath('TASK_TYPE_SCRIPT', {
                    abspos: {
                        x: 15,
                        y: 20
                    }
                });

                /* script path */ drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                return task;
            },
            'bpmn:BusinessRuleTask': function(parentGfx, element) {
                var task = renderer('bpmn:Task')(parentGfx, element);

                var headerPathData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_HEADER', {
                    abspos: {
                        x: 8,
                        y: 8
                    }
                });

                var businessHeaderPath = drawPath(parentGfx, headerPathData);
                attr$1(businessHeaderPath, {
                    strokeWidth: 1,
                    fill: getFillColor(element, '#aaaaaa'),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                var headerData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_MAIN', {
                    abspos: {
                        x: 8,
                        y: 8
                    }
                });

                var businessPath = drawPath(parentGfx, headerData);
                attr$1(businessPath, {
                    strokeWidth: 1,
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                return task;
            },
            'bpmn:SubProcess': function(parentGfx, element, attrs) {
                attrs = assign({
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                }, attrs);

                var rect = renderer('bpmn:Activity')(parentGfx, element, attrs);

                var expanded = isExpanded(element);

                if (isEventSubProcess(element)) {
                    attr$1(rect, {
                        strokeDasharray: '1,2'
                    });
                }

                renderEmbeddedLabel(parentGfx, element, expanded ? 'center-top' : 'center-middle');

                if (expanded) {
                    attachTaskMarkers(parentGfx, element);
                } else {
                    attachTaskMarkers(parentGfx, element, ['SubProcessMarker']);
                }

                return rect;
            },
            'bpmn:AdHocSubProcess': function(parentGfx, element) {
                return renderer('bpmn:SubProcess')(parentGfx, element);
            },
            'bpmn:Transaction': function(parentGfx, element) {
                var outer = renderer('bpmn:SubProcess')(parentGfx, element);

                var innerAttrs = styles.style([ 'no-fill', 'no-events' ], {
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                /* inner path */ drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS - 2, INNER_OUTER_DIST, innerAttrs);

                return outer;
            },
            'bpmn:CallActivity': function(parentGfx, element) {
                return renderer('bpmn:SubProcess')(parentGfx, element, {
                    strokeWidth: 5
                });
            },
            'bpmn:Participant': function(parentGfx, element) {

                var attrs = {
                    fillOpacity: DEFAULT_FILL_OPACITY,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                };

                var lane = renderer('bpmn:Lane')(parentGfx, element, attrs);

                var expandedPool = isExpanded(element);

                if (expandedPool) {
                    drawLine(parentGfx, [
                        { x: 30, y: 0 },
                        { x: 30, y: element.height }
                    ], {
                        stroke: getStrokeColor(element, defaultStrokeColor)
                    });
                    var text = getSemantic(element).name;
                    renderLaneLabel(parentGfx, text, element);
                } else {
                    // Collapsed pool draw text inline
                    var text2 = getSemantic(element).name;
                    renderLabel(parentGfx, text2, {
                        box: element, align: 'center-middle',
                        style: {
                            fill: getStrokeColor(element, defaultStrokeColor)
                        }
                    });
                }

                var participantMultiplicity = !!(getSemantic(element).participantMultiplicity);

                if (participantMultiplicity) {
                    renderer('ParticipantMultiplicityMarker')(parentGfx, element);
                }

                return lane;
            },
            'bpmn:Lane': function(parentGfx, element, attrs) {
                var rect = drawRect(parentGfx, element.width, element.height, 0, assign({
                    fill: getFillColor(element, defaultFillColor),
                    fillOpacity: HIGH_FILL_OPACITY,
                    stroke: getStrokeColor(element, defaultStrokeColor)
                }, attrs));

                var semantic = getSemantic(element);

                if (semantic.$type === 'bpmn:Lane') {
                    var text = semantic.name;
                    renderLaneLabel(parentGfx, text, element);
                }

                return rect;
            },
            'bpmn:InclusiveGateway': function(parentGfx, element) {
                var diamond = renderer('bpmn:Gateway')(parentGfx, element);

                /* circle path */
                drawCircle(parentGfx, element.width, element.height, element.height * 0.24, {
                    strokeWidth: 2.5,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                return diamond;
            },
            'bpmn:ExclusiveGateway': function(parentGfx, element) {
                var diamond = renderer('bpmn:Gateway')(parentGfx, element);

                var pathData = pathMap.getScaledPath('GATEWAY_EXCLUSIVE', {
                    xScaleFactor: 0.4,
                    yScaleFactor: 0.4,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: 0.32,
                        my: 0.3
                    }
                });

                if ((getDi(element).isMarkerVisible)) {
                    drawPath(parentGfx, pathData, {
                        strokeWidth: 1,
                        fill: getStrokeColor(element, defaultStrokeColor),
                        stroke: getStrokeColor(element, defaultStrokeColor)
                    });
                }

                return diamond;
            },
            'bpmn:ComplexGateway': function(parentGfx, element) {
                var diamond = renderer('bpmn:Gateway')(parentGfx, element);

                var pathData = pathMap.getScaledPath('GATEWAY_COMPLEX', {
                    xScaleFactor: 0.5,
                    yScaleFactor:0.5,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: 0.46,
                        my: 0.26
                    }
                });

                /* complex path */ drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: getStrokeColor(element, defaultStrokeColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                return diamond;
            },
            'bpmn:ParallelGateway': function(parentGfx, element) {
                var diamond = renderer('bpmn:Gateway')(parentGfx, element);

                var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', {
                    xScaleFactor: 0.6,
                    yScaleFactor:0.6,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: 0.46,
                        my: 0.2
                    }
                });

                /* parallel path */ drawPath(parentGfx, pathData, {
                    strokeWidth: 1,
                    fill: getStrokeColor(element, defaultStrokeColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                return diamond;
            },
            'bpmn:EventBasedGateway': function(parentGfx, element) {

                var semantic = getSemantic(element);

                var diamond = renderer('bpmn:Gateway')(parentGfx, element);

                /* outer circle path */ drawCircle(parentGfx, element.width, element.height, element.height * 0.20, {
                    strokeWidth: 1,
                    fill: 'none',
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                var type = semantic.eventGatewayType;
                var instantiate = !!semantic.instantiate;

                function drawEvent() {

                    var pathData = pathMap.getScaledPath('GATEWAY_EVENT_BASED', {
                        xScaleFactor: 0.18,
                        yScaleFactor: 0.18,
                        containerWidth: element.width,
                        containerHeight: element.height,
                        position: {
                            mx: 0.36,
                            my: 0.44
                        }
                    });

                    var attrs = {
                        strokeWidth: 2,
                        fill: getFillColor(element, 'none'),
                        stroke: getStrokeColor(element, defaultStrokeColor)
                    };

                    /* event path */ drawPath(parentGfx, pathData, attrs);
                }

                if (type === 'Parallel') {

                    var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', {
                        xScaleFactor: 0.4,
                        yScaleFactor:0.4,
                        containerWidth: element.width,
                        containerHeight: element.height,
                        position: {
                            mx: 0.474,
                            my: 0.296
                        }
                    });

                    var parallelPath = drawPath(parentGfx, pathData);
                    attr$1(parallelPath, {
                        strokeWidth: 1,
                        fill: 'none'
                    });
                } else if (type === 'Exclusive') {

                    if (!instantiate) {
                        var innerCircle = drawCircle(parentGfx, element.width, element.height, element.height * 0.26);
                        attr$1(innerCircle, {
                            strokeWidth: 1,
                            fill: 'none',
                            stroke: getStrokeColor(element, defaultStrokeColor)
                        });
                    }

                    drawEvent();
                }


                return diamond;
            },
            'bpmn:Gateway': function(parentGfx, element) {
                var attrs = {
                    fill: getFillColor(element, defaultFillColor),
                    fillOpacity: DEFAULT_FILL_OPACITY,
                    stroke: getStrokeColor(element, defaultStrokeColor)
                };

                return drawDiamond(parentGfx, element.width, element.height, attrs);
            },
            'bpmn:SequenceFlow': function(parentGfx, element) {
                var pathData = createPathFromConnection(element);

                var fill = getFillColor(element, defaultFillColor),
                    stroke = getStrokeColor(element, defaultStrokeColor);

                var attrs = {
                    strokeLinejoin: 'round',
                    markerEnd: marker('sequenceflow-end', fill, stroke),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                };

                var path = drawPath(parentGfx, pathData, attrs);

                var sequenceFlow = getSemantic(element);

                var source;

                if (element.source) {
                    source = element.source.businessObject;

                    // conditional flow marker
                    if (sequenceFlow.conditionExpression && source.$instanceOf('bpmn:Activity')) {
                        attr$1(path, {
                            markerStart: marker('conditional-flow-marker', fill, stroke)
                        });
                    }

                    // default marker
                    if (source.default && (source.$instanceOf('bpmn:Gateway') || source.$instanceOf('bpmn:Activity')) &&
                        source.default === sequenceFlow) {
                        attr$1(path, {
                            markerStart: marker('conditional-default-flow-marker', fill, stroke)
                        });
                    }
                }

                return path;
            },
            'bpmn:Association': function(parentGfx, element, attrs) {

                var semantic = getSemantic(element);

                var fill = getFillColor(element, defaultFillColor),
                    stroke = getStrokeColor(element, defaultStrokeColor);

                attrs = assign({
                    strokeDasharray: '0.5, 5',
                    strokeLinecap: 'round',
                    strokeLinejoin: 'round',
                    stroke: getStrokeColor(element, defaultStrokeColor)
                }, attrs || {});

                if (semantic.associationDirection === 'One' ||
                    semantic.associationDirection === 'Both') {
                    attrs.markerEnd = marker('association-end', fill, stroke);
                }

                if (semantic.associationDirection === 'Both') {
                    attrs.markerStart = marker('association-start', fill, stroke);
                }

                return drawLine(parentGfx, element.waypoints, attrs);
            },
            'bpmn:DataInputAssociation': function(parentGfx, element) {
                var fill = getFillColor(element, defaultFillColor),
                    stroke = getStrokeColor(element, defaultStrokeColor);

                return renderer('bpmn:Association')(parentGfx, element, {
                    markerEnd: marker('association-end', fill, stroke)
                });
            },
            'bpmn:DataOutputAssociation': function(parentGfx, element) {
                var fill = getFillColor(element, defaultFillColor),
                    stroke = getStrokeColor(element, defaultStrokeColor);

                return renderer('bpmn:Association')(parentGfx, element, {
                    markerEnd: marker('association-end', fill, stroke)
                });
            },
            'bpmn:MessageFlow': function(parentGfx, element) {

                var semantic = getSemantic(element),
                    di = getDi(element);

                var fill = getFillColor(element, defaultFillColor),
                    stroke = getStrokeColor(element, defaultStrokeColor);

                var pathData = createPathFromConnection(element);

                var attrs = {
                    markerEnd: marker('messageflow-end', fill, stroke),
                    markerStart: marker('messageflow-start', fill, stroke),
                    strokeDasharray: '10, 12',
                    strokeLinecap: 'round',
                    strokeLinejoin: 'round',
                    strokeWidth: '1.5px',
                    stroke: getStrokeColor(element, defaultStrokeColor)
                };

                var path = drawPath(parentGfx, pathData, attrs);

                if (semantic.messageRef) {
                    var midPoint = path.getPointAtLength(path.getTotalLength() / 2);

                    var markerPathData = pathMap.getScaledPath('MESSAGE_FLOW_MARKER', {
                        abspos: {
                            x: midPoint.x,
                            y: midPoint.y
                        }
                    });

                    var messageAttrs = { strokeWidth: 1 };

                    if (di.messageVisibleKind === 'initiating') {
                        messageAttrs.fill = 'white';
                        messageAttrs.stroke = 'black';
                    } else {
                        messageAttrs.fill = '#888';
                        messageAttrs.stroke = 'white';
                    }

                    drawPath(parentGfx, markerPathData, messageAttrs);
                }

                return path;
            },
            'bpmn:DataObject': function(parentGfx, element) {
                var pathData = pathMap.getScaledPath('DATA_OBJECT_PATH', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: 0.474,
                        my: 0.296
                    }
                });

                var elementObject = drawPath(parentGfx, pathData, {
                    fill: getFillColor(element, defaultFillColor),
                    fillOpacity: DEFAULT_FILL_OPACITY,
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                var semantic = getSemantic(element);

                if (isCollection(semantic)) {
                    renderDataItemCollection(parentGfx, element);
                }

                return elementObject;
            },
            'bpmn:DataObjectReference': as('bpmn:DataObject'),
            'bpmn:DataInput': function(parentGfx, element) {

                var arrowPathData = pathMap.getRawPath('DATA_ARROW');

                // page
                var elementObject = renderer('bpmn:DataObject')(parentGfx, element);

                /* input arrow path */ drawPath(parentGfx, arrowPathData, { strokeWidth: 1 });

                return elementObject;
            },
            'bpmn:DataOutput': function(parentGfx, element) {
                var arrowPathData = pathMap.getRawPath('DATA_ARROW');

                // page
                var elementObject = renderer('bpmn:DataObject')(parentGfx, element);

                /* output arrow path */ drawPath(parentGfx, arrowPathData, {
                    strokeWidth: 1,
                    fill: 'black'
                });

                return elementObject;
            },
            'bpmn:DataStoreReference': function(parentGfx, element) {
                var DATA_STORE_PATH = pathMap.getScaledPath('DATA_STORE', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: 0,
                        my: 0.133
                    }
                });

                var elementStore = drawPath(parentGfx, DATA_STORE_PATH, {
                    strokeWidth: 2,
                    fill: getFillColor(element, defaultFillColor),
                    fillOpacity: DEFAULT_FILL_OPACITY,
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                return elementStore;
            },
            'bpmn:BoundaryEvent': function(parentGfx, element) {

                var semantic = getSemantic(element),
                    cancel = semantic.cancelActivity;

                var attrs = {
                    strokeWidth: 1,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                };

                if (!cancel) {
                    attrs.strokeDasharray = '6';
                    attrs.strokeLinecap = 'round';
                }

                // apply fillOpacity
                var outerAttrs = assign({}, attrs, {
                    fillOpacity: 1
                });

                // apply no-fill
                var innerAttrs = assign({}, attrs, {
                    fill: 'none'
                });

                var outer = renderer('bpmn:Event')(parentGfx, element, outerAttrs);

                /* inner path */ drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, innerAttrs);

                renderEventContent(element, parentGfx);

                return outer;
            },
            'bpmn:Group': function(parentGfx, element) {

                var group = drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, {
                    strokeWidth: 1,
                    strokeDasharray: '8,3,1,3',
                    fill: 'none',
                    pointerEvents: 'none'
                });

                return group;
            },
            'label': function(parentGfx, element) {
                return renderExternalLabel(parentGfx, element);
            },
            'bpmn:TextAnnotation': function(parentGfx, element) {
                var style = {
                    'fill': 'none',
                    'stroke': 'none'
                };

                var textElement = drawRect(parentGfx, element.width, element.height, 0, 0, style);

                var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: 0.0,
                        my: 0.0
                    }
                });

                drawPath(parentGfx, textPathData, {
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                var text = getSemantic(element).text || '';
                renderLabel(parentGfx, text, {
                    box: element,
                    align: 'left-top',
                    padding: 5,
                    style: {
                        fill: getStrokeColor(element, defaultStrokeColor)
                    }
                });

                return textElement;
            },
            'ParticipantMultiplicityMarker': function(parentGfx, element) {
                var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: ((element.width / 2) / element.width),
                        my: (element.height - 15) / element.height
                    }
                });

                drawMarker('participant-multiplicity', parentGfx, markerPath, {
                    strokeWidth: 1,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });
            },
            'SubProcessMarker': function(parentGfx, element) {
                var markerRect = drawRect(parentGfx, 14, 14, 0, {
                    strokeWidth: 1,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });

                // Process marker is placed in the middle of the box
                // therefore fixed values can be used here
                translate(markerRect, element.width / 2 - 7.5, element.height - 20);

                var markerPath = pathMap.getScaledPath('MARKER_SUB_PROCESS', {
                    xScaleFactor: 1.5,
                    yScaleFactor: 1.5,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: (element.width / 2 - 7.5) / element.width,
                        my: (element.height - 20) / element.height
                    }
                });

                drawMarker('sub-process', parentGfx, markerPath, {
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });
            },
            'ParallelMarker': function(parentGfx, element, position) {
                var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: ((element.width / 2 + position.parallel) / element.width),
                        my: (element.height - 20) / element.height
                    }
                });

                drawMarker('parallel', parentGfx, markerPath, {
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });
            },
            'SequentialMarker': function(parentGfx, element, position) {
                var markerPath = pathMap.getScaledPath('MARKER_SEQUENTIAL', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: ((element.width / 2 + position.seq) / element.width),
                        my: (element.height - 19) / element.height
                    }
                });

                drawMarker('sequential', parentGfx, markerPath, {
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });
            },
            'CompensationMarker': function(parentGfx, element, position) {
                var markerMath = pathMap.getScaledPath('MARKER_COMPENSATION', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: ((element.width / 2 + position.compensation) / element.width),
                        my: (element.height - 13) / element.height
                    }
                });

                drawMarker('compensation', parentGfx, markerMath, {
                    strokeWidth: 1,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });
            },
            'LoopMarker': function(parentGfx, element, position) {
                var markerPath = pathMap.getScaledPath('MARKER_LOOP', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: ((element.width / 2 + position.loop) / element.width),
                        my: (element.height - 7) / element.height
                    }
                });

                drawMarker('loop', parentGfx, markerPath, {
                    strokeWidth: 1,
                    fill: getFillColor(element, defaultFillColor),
                    stroke: getStrokeColor(element, defaultStrokeColor),
                    strokeLinecap: 'round',
                    strokeMiterlimit: 0.5
                });
            },
            'AdhocMarker': function(parentGfx, element, position) {
                var markerPath = pathMap.getScaledPath('MARKER_ADHOC', {
                    xScaleFactor: 1,
                    yScaleFactor: 1,
                    containerWidth: element.width,
                    containerHeight: element.height,
                    position: {
                        mx: ((element.width / 2 + position.adhoc) / element.width),
                        my: (element.height - 15) / element.height
                    }
                });

                drawMarker('adhoc', parentGfx, markerPath, {
                    strokeWidth: 1,
                    fill: getStrokeColor(element, defaultStrokeColor),
                    stroke: getStrokeColor(element, defaultStrokeColor)
                });
            }
        };

        function attachTaskMarkers(parentGfx, element, taskMarkers) {
            var obj = getSemantic(element);

            var subprocess = taskMarkers && taskMarkers.indexOf('SubProcessMarker') !== -1;
            var position;

            if (subprocess) {
                position = {
                    seq: -21,
                    parallel: -22,
                    compensation: -42,
                    loop: -18,
                    adhoc: 10
                };
            } else {
                position = {
                    seq: -3,
                    parallel: -6,
                    compensation: -27,
                    loop: 0,
                    adhoc: 10
                };
            }

            forEach(taskMarkers, function(marker) {
                renderer(marker)(parentGfx, element, position);
            });

            if (obj.isForCompensation) {
                renderer('CompensationMarker')(parentGfx, element, position);
            }

            if (obj.$type === 'bpmn:AdHocSubProcess') {
                renderer('AdhocMarker')(parentGfx, element, position);
            }

            var loopCharacteristics = obj.loopCharacteristics,
                isSequential = loopCharacteristics && loopCharacteristics.isSequential;

            if (loopCharacteristics) {

                if (isSequential === undefined) {
                    renderer('LoopMarker')(parentGfx, element, position);
                }

                if (isSequential === false) {
                    renderer('ParallelMarker')(parentGfx, element, position);
                }

                if (isSequential === true) {
                    renderer('SequentialMarker')(parentGfx, element, position);
                }
            }
        }

        function renderDataItemCollection(parentGfx, element) {

            var yPosition = (element.height - 16) / element.height;

            var pathData = pathMap.getScaledPath('DATA_OBJECT_COLLECTION_PATH', {
                xScaleFactor: 1,
                yScaleFactor: 1,
                containerWidth: element.width,
                containerHeight: element.height,
                position: {
                    mx: 0.451,
                    my: yPosition
                }
            });

            /* collection path */ drawPath(parentGfx, pathData, {
                strokeWidth: 2
            });
        }


        // extension API, use at your own risk
        this._drawPath = drawPath;

    }


    inherits_browser(BpmnRenderer, BaseRenderer);

    BpmnRenderer.$inject = [
        'config.bpmnRenderer',
        'eventBus',
        'styles',
        'pathMap',
        'canvas',
        'textRenderer'
    ];


    BpmnRenderer.prototype.canRender = function(element) {
        return is$1(element, 'bpmn:BaseElement');
    };

    BpmnRenderer.prototype.drawShape = function(parentGfx, element) {
        var type = element.type;
        var h = this.handlers[type];

        /* jshint -W040 */
        return h(parentGfx, element);
    };

    BpmnRenderer.prototype.drawConnection = function(parentGfx, element) {
        var type = element.type;
        var h = this.handlers[type];

        /* jshint -W040 */
        return h(parentGfx, element);
    };

    BpmnRenderer.prototype.getShapePath = function(element) {

        if (is$1(element, 'bpmn:Event')) {
            return getCirclePath(element);
        }

        if (is$1(element, 'bpmn:Activity')) {
            return getRoundRectPath(element, TASK_BORDER_RADIUS);
        }

        if (is$1(element, 'bpmn:Gateway')) {
            return getDiamondPath(element);
        }

        return getRectPath(element);
    };

    var DEFAULT_BOX_PADDING = 0;

    var DEFAULT_LABEL_SIZE = {
        width: 150,
        height: 50
    };


    function parseAlign(align) {

        var parts = align.split('-');

        return {
            horizontal: parts[0] || 'center',
            vertical: parts[1] || 'top'
        };
    }

    function parsePadding(padding) {

        if (isObject(padding)) {
            return assign({ top: 0, left: 0, right: 0, bottom: 0 }, padding);
        } else {
            return {
                top: padding,
                left: padding,
                right: padding,
                bottom: padding
            };
        }
    }

    function getTextBBox(text, fakeText) {

        fakeText.textContent = text;

        var textBBox;

        try {
            var bbox,
                emptyLine = text === '';

            // add dummy text, when line is empty to
            // determine correct height
            fakeText.textContent = emptyLine ? 'dummy' : text;

            textBBox = fakeText.getBBox();

            // take text rendering related horizontal
            // padding into account
            bbox = {
                width: textBBox.width + textBBox.x * 2,
                height: textBBox.height
            };

            if (emptyLine) {
                // correct width
                bbox.width = 0;
            }

            return bbox;
        } catch (e) {
            return { width: 0, height: 0 };
        }
    }


    /**
     * Layout the next line and return the layouted element.
     *
     * Alters the lines passed.
     *
     * @param  {Array<String>} lines
     * @return {Object} the line descriptor, an object { width, height, text }
     */
    function layoutNext(lines, maxWidth, fakeText) {

        var originalLine = lines.shift(),
            fitLine = originalLine;

        var textBBox;

        for (;;) {
            textBBox = getTextBBox(fitLine, fakeText);

            textBBox.width = fitLine ? textBBox.width : 0;

            // try to fit
            if (fitLine === ' ' || fitLine === '' || textBBox.width < Math.round(maxWidth) || fitLine.length < 2) {
                return fit(lines, fitLine, originalLine, textBBox);
            }

            fitLine = shortenLine(fitLine, textBBox.width, maxWidth);
        }
    }

    function fit(lines, fitLine, originalLine, textBBox) {
        if (fitLine.length < originalLine.length) {
            var remainder = originalLine.slice(fitLine.length).trim();

            lines.unshift(remainder);
        }

        return {
            width: textBBox.width,
            height: textBBox.height,
            text: fitLine
        };
    }


    /**
     * Shortens a line based on spacing and hyphens.
     * Returns the shortened result on success.
     *
     * @param  {String} line
     * @param  {Number} maxLength the maximum characters of the string
     * @return {String} the shortened string
     */
    function semanticShorten(line, maxLength) {
        var parts = line.split(/(\s|-)/g),
            part,
            shortenedParts = [],
            length = 0;

        // try to shorten via spaces + hyphens
        if (parts.length > 1) {
            while ((part = parts.shift())) {
                if (part.length + length < maxLength) {
                    shortenedParts.push(part);
                    length += part.length;
                } else {
                    // remove previous part, too if hyphen does not fit anymore
                    if (part === '-') {
                        shortenedParts.pop();
                    }

                    break;
                }
            }
        }

        return shortenedParts.join('');
    }


    function shortenLine(line, width, maxWidth) {
        var length = Math.max(line.length * (maxWidth / width), 1);

        // try to shorten semantically (i.e. based on spaces and hyphens)
        var shortenedLine = semanticShorten(line, length);

        if (!shortenedLine) {

            // force shorten by cutting the long word
            shortenedLine = line.slice(0, Math.max(Math.round(length - 1), 1));
        }

        return shortenedLine;
    }


    function getHelperSvg() {
        var helperSvg = document.getElementById('helper-svg');

        if (!helperSvg) {
            helperSvg = create('svg');

            attr$1(helperSvg, {
                id: 'helper-svg',
                width: 0,
                height: 0,
                style: 'visibility: hidden; position: fixed'
            });

            document.body.appendChild(helperSvg);
        }

        return helperSvg;
    }


    /**
     * Creates a new label utility
     *
     * @param {Object} config
     * @param {Dimensions} config.size
     * @param {Number} config.padding
     * @param {Object} config.style
     * @param {String} config.align
     */
    function Text(config) {

        this._config = assign({}, {
            size: DEFAULT_LABEL_SIZE,
            padding: DEFAULT_BOX_PADDING,
            style: {},
            align: 'center-top'
        }, config || {});
    }

    /**
     * Returns the layouted text as an SVG element.
     *
     * @param {String} text
     * @param {Object} options
     *
     * @return {SVGElement}
     */
    Text.prototype.createText = function(text, options) {
        return this.layoutText(text, options).element;
    };

    /**
     * Returns a labels layouted dimensions.
     *
     * @param {String} text to layout
     * @param {Object} options
     *
     * @return {Dimensions}
     */
    Text.prototype.getDimensions = function(text, options) {
        return this.layoutText(text, options).dimensions;
    };

    /**
     * Creates and returns a label and its bounding box.
     *
     * @method Text#createText
     *
     * @param {String} text the text to render on the label
     * @param {Object} options
     * @param {String} options.align how to align in the bounding box.
     *                               Any of { 'center-middle', 'center-top' },
     *                               defaults to 'center-top'.
     * @param {String} options.style style to be applied to the text
     * @param {boolean} options.fitBox indicates if box will be recalculated to
     *                                 fit text
     *
     * @return {Object} { element, dimensions }
     */
    Text.prototype.layoutText = function(text, options) {
        var box = assign({}, this._config.size, options.box),
            style = assign({}, this._config.style, options.style),
            align = parseAlign(options.align || this._config.align),
            padding = parsePadding(options.padding !== undefined ? options.padding : this._config.padding),
            fitBox = options.fitBox || false;

        var lineHeight = getLineHeight(style);

        var lines = text.split(/\r?\n/g),
            layouted = [];

        var maxWidth = box.width - padding.left - padding.right;

        // ensure correct rendering by attaching helper text node to invisible SVG
        var helperText = create('text');
        attr$1(helperText, { x: 0, y: 0 });
        attr$1(helperText, style);

        var helperSvg = getHelperSvg();

        append(helperSvg, helperText);

        while (lines.length) {
            layouted.push(layoutNext(lines, maxWidth, helperText));
        }

        if (align.vertical === 'middle') {
            padding.top = padding.bottom = 0;
        }

        var totalHeight = reduce(layouted, function(sum, line, idx) {
            return sum + (lineHeight || line.height);
        }, 0) + padding.top + padding.bottom;

        var maxLineWidth = reduce(layouted, function(sum, line, idx) {
            return line.width > sum ? line.width : sum;
        }, 0);

        // the y position of the next line
        var y = padding.top;

        if (align.vertical === 'middle') {
            y += (box.height - totalHeight) / 2;
        }

        // magic number initial offset
        y -= (lineHeight || layouted[0].height) / 4;


        var textElement = create('text');

        attr$1(textElement, style);

        // layout each line taking into account that parent
        // shape might resize to fit text size
        forEach(layouted, function(line) {

            var x;

            y += (lineHeight || line.height);

            switch (align.horizontal) {
                case 'left':
                    x = padding.left;
                    break;

                case 'right':
                    x = ((fitBox ? maxLineWidth : maxWidth)
                        - padding.right - line.width);
                    break;

                default:
                    // aka center
                    x = Math.max((((fitBox ? maxLineWidth : maxWidth)
                        - line.width) / 2 + padding.left), 0);
            }

            var tspan = create('tspan');
            attr$1(tspan, { x: x, y: y });

            tspan.textContent = line.text;

            append(textElement, tspan);
        });

        remove$1(helperText);

        var dimensions = {
            width: maxLineWidth,
            height: totalHeight
        };

        return {
            dimensions: dimensions,
            element: textElement
        };
    };


    function getLineHeight(style) {
        if ('fontSize' in style && 'lineHeight' in style) {
            return style.lineHeight * parseInt(style.fontSize, 10);
        }
    }

    var DEFAULT_FONT_SIZE = 12;
    var LINE_HEIGHT_RATIO = 1.2;

    var MIN_TEXT_ANNOTATION_HEIGHT = 30;


    function TextRenderer(config) {

        var defaultStyle = assign({
            fontFamily: 'Arial, sans-serif',
            fontSize: DEFAULT_FONT_SIZE,
            fontWeight: 'normal',
            lineHeight: LINE_HEIGHT_RATIO
        }, config && config.defaultStyle || {});

        var fontSize = parseInt(defaultStyle.fontSize, 10) - 1;

        var externalStyle = assign({}, defaultStyle, {
            fontSize: fontSize
        }, config && config.externalStyle || {});

        var textUtil = new Text({
            style: defaultStyle
        });

        /**
         * Get the new bounds of an externally rendered,
         * layouted label.
         *
         * @param  {Bounds} bounds
         * @param  {String} text
         *
         * @return {Bounds}
         */
        this.getExternalLabelBounds = function(bounds, text) {

            var layoutedDimensions = textUtil.getDimensions(text, {
                box: {
                    width: 90,
                    height: 30,
                    x: bounds.width / 2 + bounds.x,
                    y: bounds.height / 2 + bounds.y
                },
                style: externalStyle
            });

            // resize label shape to fit label text
            return {
                x: Math.round(bounds.x + bounds.width / 2 - layoutedDimensions.width / 2),
                y: Math.round(bounds.y),
                width: Math.ceil(layoutedDimensions.width),
                height: Math.ceil(layoutedDimensions.height)
            };

        };

        /**
         * Get the new bounds of text annotation.
         *
         * @param  {Bounds} bounds
         * @param  {String} text
         *
         * @return {Bounds}
         */
        this.getTextAnnotationBounds = function(bounds, text) {

            var layoutedDimensions = textUtil.getDimensions(text, {
                box: bounds,
                style: defaultStyle,
                align: 'left-top',
                padding: 5
            });

            return {
                x: bounds.x,
                y: bounds.y,
                width: bounds.width,
                height: Math.max(MIN_TEXT_ANNOTATION_HEIGHT, Math.round(layoutedDimensions.height))
            };
        };

        /**
         * Create a layouted text element.
         *
         * @param {String} text
         * @param {Object} [options]
         *
         * @return {SVGElement} rendered text
         */
        this.createText = function(text, options) {
            return textUtil.createText(text, options || {});
        };

        /**
         * Get default text style.
         */
        this.getDefaultStyle = function() {
            return defaultStyle;
        };

        /**
         * Get the external text style.
         */
        this.getExternalStyle = function() {
            return externalStyle;
        };

    }

    TextRenderer.$inject = [
        'config.textRenderer'
    ];

    /**
     * Map containing SVG paths needed by BpmnRenderer.
     */

    function PathMap() {

        /**
         * Contains a map of path elements
         *
         * <h1>Path definition</h1>
         * A parameterized path is defined like this:
         * <pre>
         * 'GATEWAY_PARALLEL': {
         *   d: 'm {mx},{my} {e.x0},0 0,{e.x1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
	          '-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
         *   height: 17.5,
         *   width:  17.5,
         *   heightElements: [2.5, 7.5],
         *   widthElements: [2.5, 7.5]
         * }
         * </pre>
         * <p>It's important to specify a correct <b>height and width</b> for the path as the scaling
         * is based on the ratio between the specified height and width in this object and the
         * height and width that is set as scale target (Note x,y coordinates will be scaled with
         * individual ratios).</p>
         * <p>The '<b>heightElements</b>' and '<b>widthElements</b>' array must contain the values that will be scaled.
         * The scaling is based on the computed ratios.
         * Coordinates on the y axis should be in the <b>heightElement</b>'s array, they will be scaled using
         * the computed ratio coefficient.
         * In the parameterized path the scaled values can be accessed through the 'e' object in {} brackets.
         *   <ul>
         *    <li>The values for the y axis can be accessed in the path string using {e.y0}, {e.y1}, ....</li>
         *    <li>The values for the x axis can be accessed in the path string using {e.x0}, {e.x1}, ....</li>
         *   </ul>
         *   The numbers x0, x1 respectively y0, y1, ... map to the corresponding array index.
         * </p>
         */
        this.pathMap = {
            'EVENT_MESSAGE': {
                d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}',
                height: 36,
                width:  36,
                heightElements: [6, 14],
                widthElements: [10.5, 21]
            },
            'EVENT_SIGNAL': {
                d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x1},0 Z',
                height: 36,
                width: 36,
                heightElements: [18],
                widthElements: [10, 20]
            },
            'EVENT_ESCALATION': {
                d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x0},-{e.y1} l -{e.x0},{e.y1} Z',
                height: 36,
                width: 36,
                heightElements: [20, 7],
                widthElements: [8]
            },
            'EVENT_CONDITIONAL': {
                d: 'M {e.x0},{e.y0} l {e.x1},0 l 0,{e.y2} l -{e.x1},0 Z ' +
                    'M {e.x2},{e.y3} l {e.x0},0 ' +
                    'M {e.x2},{e.y4} l {e.x0},0 ' +
                    'M {e.x2},{e.y5} l {e.x0},0 ' +
                    'M {e.x2},{e.y6} l {e.x0},0 ' +
                    'M {e.x2},{e.y7} l {e.x0},0 ' +
                    'M {e.x2},{e.y8} l {e.x0},0 ',
                height: 36,
                width:  36,
                heightElements: [8.5, 14.5, 18, 11.5, 14.5, 17.5, 20.5, 23.5, 26.5],
                widthElements:  [10.5, 14.5, 12.5]
            },
            'EVENT_LINK': {
                d: 'm {mx},{my} 0,{e.y0} -{e.x1},0 0,{e.y1} {e.x1},0 0,{e.y0} {e.x0},-{e.y2} -{e.x0},-{e.y2} z',
                height: 36,
                width: 36,
                heightElements: [4.4375, 6.75, 7.8125],
                widthElements: [9.84375, 13.5]
            },
            'EVENT_ERROR': {
                d: 'm {mx},{my} {e.x0},-{e.y0} {e.x1},-{e.y1} {e.x2},{e.y2} {e.x3},-{e.y3} -{e.x4},{e.y4} -{e.x5},-{e.y5} z',
                height: 36,
                width: 36,
                heightElements: [0.023, 8.737, 8.151, 16.564, 10.591, 8.714],
                widthElements: [0.085, 6.672, 6.97, 4.273, 5.337, 6.636]
            },
            'EVENT_CANCEL_45': {
                d: 'm {mx},{my} -{e.x1},0 0,{e.x0} {e.x1},0 0,{e.y1} {e.x0},0 ' +
                    '0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z',
                height: 36,
                width: 36,
                heightElements: [4.75, 8.5],
                widthElements: [4.75, 8.5]
            },
            'EVENT_COMPENSATION': {
                d: 'm {mx},{my} {e.x0},-{e.y0} 0,{e.y1} z m {e.x1},-{e.y2} {e.x2},-{e.y3} 0,{e.y1} -{e.x2},-{e.y3} z',
                height: 36,
                width: 36,
                heightElements: [6.5, 13, 0.4, 6.1],
                widthElements: [9, 9.3, 8.7]
            },
            'EVENT_TIMER_WH': {
                d: 'M {mx},{my} l {e.x0},-{e.y0} m -{e.x0},{e.y0} l {e.x1},{e.y1} ',
                height: 36,
                width:  36,
                heightElements: [10, 2],
                widthElements: [3, 7]
            },
            'EVENT_TIMER_LINE': {
                d:  'M {mx},{my} ' +
                    'm {e.x0},{e.y0} l -{e.x1},{e.y1} ',
                height: 36,
                width:  36,
                heightElements: [10, 3],
                widthElements: [0, 0]
            },
            'EVENT_MULTIPLE': {
                d:'m {mx},{my} {e.x1},-{e.y0} {e.x1},{e.y0} -{e.x0},{e.y1} -{e.x2},0 z',
                height: 36,
                width:  36,
                heightElements: [6.28099, 12.56199],
                widthElements: [3.1405, 9.42149, 12.56198]
            },
            'EVENT_PARALLEL_MULTIPLE': {
                d:'m {mx},{my} {e.x0},0 0,{e.y1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
                    '-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
                height: 36,
                width:  36,
                heightElements: [2.56228, 7.68683],
                widthElements: [2.56228, 7.68683]
            },
            'GATEWAY_EXCLUSIVE': {
                d:'m {mx},{my} {e.x0},{e.y0} {e.x1},{e.y0} {e.x2},0 {e.x4},{e.y2} ' +
                    '{e.x4},{e.y1} {e.x2},0 {e.x1},{e.y3} {e.x0},{e.y3} ' +
                    '{e.x3},0 {e.x5},{e.y1} {e.x5},{e.y2} {e.x3},0 z',
                height: 17.5,
                width:  17.5,
                heightElements: [8.5, 6.5312, -6.5312, -8.5],
                widthElements:  [6.5, -6.5, 3, -3, 5, -5]
            },
            'GATEWAY_PARALLEL': {
                d:'m {mx},{my} 0,{e.y1} -{e.x1},0 0,{e.y0} {e.x1},0 0,{e.y1} {e.x0},0 ' +
                    '0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z',
                height: 30,
                width:  30,
                heightElements: [5, 12.5],
                widthElements: [5, 12.5]
            },
            'GATEWAY_EVENT_BASED': {
                d:'m {mx},{my} {e.x0},{e.y0} {e.x0},{e.y1} {e.x1},{e.y2} {e.x2},0 z',
                height: 11,
                width:  11,
                heightElements: [-6, 6, 12, -12],
                widthElements: [9, -3, -12]
            },
            'GATEWAY_COMPLEX': {
                d:'m {mx},{my} 0,{e.y0} -{e.x0},-{e.y1} -{e.x1},{e.y2} {e.x0},{e.y1} -{e.x2},0 0,{e.y3} ' +
                    '{e.x2},0  -{e.x0},{e.y1} l {e.x1},{e.y2} {e.x0},-{e.y1} 0,{e.y0} {e.x3},0 0,-{e.y0} {e.x0},{e.y1} ' +
                    '{e.x1},-{e.y2} -{e.x0},-{e.y1} {e.x2},0 0,-{e.y3} -{e.x2},0 {e.x0},-{e.y1} -{e.x1},-{e.y2} ' +
                    '-{e.x0},{e.y1} 0,-{e.y0} -{e.x3},0 z',
                height: 17.125,
                width:  17.125,
                heightElements: [4.875, 3.4375, 2.125, 3],
                widthElements: [3.4375, 2.125, 4.875, 3]
            },
            'DATA_OBJECT_PATH': {
                d:'m 0,0 {e.x1},0 {e.x0},{e.y0} 0,{e.y1} -{e.x2},0 0,-{e.y2} {e.x1},0 0,{e.y0} {e.x0},0',
                height: 61,
                width:  51,
                heightElements: [10, 50, 60],
                widthElements: [10, 40, 50, 60]
            },
            'DATA_OBJECT_COLLECTION_PATH': {
                d:'m {mx}, {my} ' +
                    'm  0 15  l 0 -15 ' +
                    'm  4 15  l 0 -15 ' +
                    'm  4 15  l 0 -15 ',
                height: 61,
                width:  51,
                heightElements: [12],
                widthElements: [1, 6, 12, 15]
            },
            'DATA_ARROW': {
                d:'m 5,9 9,0 0,-3 5,5 -5,5 0,-3 -9,0 z',
                height: 61,
                width:  51,
                heightElements: [],
                widthElements: []
            },
            'DATA_STORE': {
                d:'m  {mx},{my} ' +
                    'l  0,{e.y2} ' +
                    'c  {e.x0},{e.y1} {e.x1},{e.y1}  {e.x2},0 ' +
                    'l  0,-{e.y2} ' +
                    'c -{e.x0},-{e.y1} -{e.x1},-{e.y1} -{e.x2},0' +
                    'c  {e.x0},{e.y1} {e.x1},{e.y1}  {e.x2},0 ' +
                    'm  -{e.x2},{e.y0}' +
                    'c  {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0' +
                    'm  -{e.x2},{e.y0}' +
                    'c  {e.x0},{e.y1} {e.x1},{e.y1}  {e.x2},0',
                height: 61,
                width:  61,
                heightElements: [7, 10, 45],
                widthElements:  [2, 58, 60]
            },
            'TEXT_ANNOTATION': {
                d: 'm {mx}, {my} m 10,0 l -10,0 l 0,{e.y0} l 10,0',
                height: 30,
                width: 10,
                heightElements: [30],
                widthElements: [10]
            },
            'MARKER_SUB_PROCESS': {
                d: 'm{mx},{my} m 7,2 l 0,10 m -5,-5 l 10,0',
                height: 10,
                width: 10,
                heightElements: [],
                widthElements: []
            },
            'MARKER_PARALLEL': {
                d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10',
                height: 10,
                width: 10,
                heightElements: [],
                widthElements: []
            },
            'MARKER_SEQUENTIAL': {
                d: 'm{mx},{my} m 0,3 l 10,0 m -10,3 l 10,0 m -10,3 l 10,0',
                height: 10,
                width: 10,
                heightElements: [],
                widthElements: []
            },
            'MARKER_COMPENSATION': {
                d: 'm {mx},{my} 7,-5 0,10 z m 7.1,-0.3 6.9,-4.7 0,10 -6.9,-4.7 z',
                height: 10,
                width: 21,
                heightElements: [],
                widthElements: []
            },
            'MARKER_LOOP': {
                d: 'm {mx},{my} c 3.526979,0 6.386161,-2.829858 6.386161,-6.320661 0,-3.490806 -2.859182,-6.320661 ' +
                    '-6.386161,-6.320661 -3.526978,0 -6.38616,2.829855 -6.38616,6.320661 0,1.745402 ' +
                    '0.714797,3.325567 1.870463,4.469381 0.577834,0.571908 1.265885,1.034728 2.029916,1.35457 ' +
                    'l -0.718163,-3.909793 m 0.718163,3.909793 -3.885211,0.802902',
                height: 13.9,
                width: 13.7,
                heightElements: [],
                widthElements: []
            },
            'MARKER_ADHOC': {
                d: 'm {mx},{my} m 0.84461,2.64411 c 1.05533,-1.23780996 2.64337,-2.07882 4.29653,-1.97997996 2.05163,0.0805 ' +
                    '3.85579,1.15803 5.76082,1.79107 1.06385,0.34139996 2.24454,0.1438 3.18759,-0.43767 0.61743,-0.33642 ' +
                    '1.2775,-0.64078 1.7542,-1.17511 0,0.56023 0,1.12046 0,1.6807 -0.98706,0.96237996 -2.29792,1.62393996 ' +
                    '-3.6918,1.66181996 -1.24459,0.0927 -2.46671,-0.2491 -3.59505,-0.74812 -1.35789,-0.55965 ' +
                    '-2.75133,-1.33436996 -4.27027,-1.18121996 -1.37741,0.14601 -2.41842,1.13685996 -3.44288,1.96782996 z',
                height: 4,
                width: 15,
                heightElements: [],
                widthElements: []
            },
            'TASK_TYPE_SEND': {
                d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}',
                height: 14,
                width:  21,
                heightElements: [6, 14],
                widthElements: [10.5, 21]
            },
            'TASK_TYPE_SCRIPT': {
                d: 'm {mx},{my} c 9.966553,-6.27276 -8.000926,-7.91932 2.968968,-14.938 l -8.802728,0 ' +
                    'c -10.969894,7.01868 6.997585,8.66524 -2.968967,14.938 z ' +
                    'm -7,-12 l 5,0 ' +
                    'm -4.5,3 l 4.5,0 ' +
                    'm -3,3 l 5,0' +
                    'm -4,3 l 5,0',
                height: 15,
                width:  12.6,
                heightElements: [6, 14],
                widthElements: [10.5, 21]
            },
            'TASK_TYPE_USER_1': {
                d: 'm {mx},{my} c 0.909,-0.845 1.594,-2.049 1.594,-3.385 0,-2.554 -1.805,-4.62199999 ' +
                    '-4.357,-4.62199999 -2.55199998,0 -4.28799998,2.06799999 -4.28799998,4.62199999 0,1.348 ' +
                    '0.974,2.562 1.89599998,3.405 -0.52899998,0.187 -5.669,2.097 -5.794,4.7560005 v 6.718 ' +
                    'h 17 v -6.718 c 0,-2.2980005 -5.5279996,-4.5950005 -6.0509996,-4.7760005 z' +
                    'm -8,6 l 0,5.5 m 11,0 l 0,-5'
            },
            'TASK_TYPE_USER_2': {
                d: 'm {mx},{my} m 2.162,1.009 c 0,2.4470005 -2.158,4.4310005 -4.821,4.4310005 ' +
                    '-2.66499998,0 -4.822,-1.981 -4.822,-4.4310005 '
            },
            'TASK_TYPE_USER_3': {
                d: 'm {mx},{my} m -6.9,-3.80 c 0,0 2.25099998,-2.358 4.27399998,-1.177 2.024,1.181 4.221,1.537 ' +
                    '4.124,0.965 -0.098,-0.57 -0.117,-3.79099999 -4.191,-4.13599999 -3.57499998,0.001 ' +
                    '-4.20799998,3.36699999 -4.20699998,4.34799999 z'
            },
            'TASK_TYPE_MANUAL': {
                d: 'm {mx},{my} c 0.234,-0.01 5.604,0.008 8.029,0.004 0.808,0 1.271,-0.172 1.417,-0.752 0.227,-0.898 ' +
                    '-0.334,-1.314 -1.338,-1.316 -2.467,-0.01 -7.886,-0.004 -8.108,-0.004 -0.014,-0.079 0.016,-0.533 0,-0.61 ' +
                    '0.195,-0.042 8.507,0.006 9.616,0.002 0.877,-0.007 1.35,-0.438 1.353,-1.208 0.003,-0.768 -0.479,-1.09 ' +
                    '-1.35,-1.091 -2.968,-0.002 -9.619,-0.013 -9.619,-0.013 v -0.591 c 0,0 5.052,-0.016 7.225,-0.016 ' +
                    '0.888,-0.002 1.354,-0.416 1.351,-1.193 -0.006,-0.761 -0.492,-1.196 -1.361,-1.196 -3.473,-0.005 ' +
                    '-10.86,-0.003 -11.0829995,-0.003 -0.022,-0.047 -0.045,-0.094 -0.069,-0.139 0.3939995,-0.319 ' +
                    '2.0409995,-1.626 2.4149995,-2.017 0.469,-0.4870005 0.519,-1.1650005 0.162,-1.6040005 -0.414,-0.511 ' +
                    '-0.973,-0.5 -1.48,-0.236 -1.4609995,0.764 -6.5999995,3.6430005 -7.7329995,4.2710005 -0.9,0.499 ' +
                    '-1.516,1.253 -1.882,2.19 -0.37000002,0.95 -0.17,2.01 -0.166,2.979 0.004,0.718 -0.27300002,1.345 ' +
                    '-0.055,2.063 0.629,2.087 2.425,3.312 4.859,3.318 4.6179995,0.014 9.2379995,-0.139 13.8569995,-0.158 ' +
                    '0.755,-0.004 1.171,-0.301 1.182,-1.033 0.012,-0.754 -0.423,-0.969 -1.183,-0.973 -1.778,-0.01 ' +
                    '-5.824,-0.004 -6.04,-0.004 10e-4,-0.084 0.003,-0.586 10e-4,-0.67 z'
            },
            'TASK_TYPE_INSTANTIATING_SEND': {
                d: 'm {mx},{my} l 0,8.4 l 12.6,0 l 0,-8.4 z l 6.3,3.6 l 6.3,-3.6'
            },
            'TASK_TYPE_SERVICE': {
                d: 'm {mx},{my} v -1.71335 c 0.352326,-0.0705 0.703932,-0.17838 1.047628,-0.32133 ' +
                    '0.344416,-0.14465 0.665822,-0.32133 0.966377,-0.52145 l 1.19431,1.18005 1.567487,-1.57688 ' +
                    '-1.195028,-1.18014 c 0.403376,-0.61394 0.683079,-1.29908 0.825447,-2.01824 l 1.622133,-0.01 ' +
                    'v -2.2196 l -1.636514,0.01 c -0.07333,-0.35153 -0.178319,-0.70024 -0.323564,-1.04372 ' +
                    '-0.145244,-0.34406 -0.321407,-0.6644 -0.522735,-0.96217 l 1.131035,-1.13631 -1.583305,-1.56293 ' +
                    '-1.129598,1.13589 c -0.614052,-0.40108 -1.302883,-0.68093 -2.022633,-0.82247 l 0.0093,-1.61852 ' +
                    'h -2.241173 l 0.0042,1.63124 c -0.353763,0.0736 -0.705369,0.17977 -1.049785,0.32371 -0.344415,0.14437 ' +
                    '-0.665102,0.32092 -0.9635006,0.52046 l -1.1698628,-1.15823 -1.5667691,1.5792 1.1684265,1.15669 ' +
                    'c -0.4026573,0.61283 -0.68308,1.29797 -0.8247287,2.01713 l -1.6588041,0.003 v 2.22174 ' +
                    'l 1.6724648,-0.006 c 0.073327,0.35077 0.1797598,0.70243 0.3242851,1.04472 0.1452428,0.34448 ' +
                    '0.3214064,0.6644 0.5227339,0.96066 l -1.1993431,1.19723 1.5840256,1.56011 1.1964668,-1.19348 ' +
                    'c 0.6140517,0.40346 1.3028827,0.68232 2.0233517,0.82331 l 7.19e-4,1.69892 h 2.226848 z ' +
                    'm 0.221462,-3.9957 c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' +
                    '0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' +
                    '0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z'
            },
            'TASK_TYPE_SERVICE_FILL': {
                d: 'm {mx},{my} c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' +
                    '0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' +
                    '0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z'
            },
            'TASK_TYPE_BUSINESS_RULE_HEADER': {
                d: 'm {mx},{my} 0,4 20,0 0,-4 z'
            },
            'TASK_TYPE_BUSINESS_RULE_MAIN': {
                d: 'm {mx},{my} 0,12 20,0 0,-12 z' +
                    'm 0,8 l 20,0 ' +
                    'm -13,-4 l 0,8'
            },
            'MESSAGE_FLOW_MARKER': {
                d: 'm {mx},{my} m -10.5 ,-7 l 0,14 l 21,0 l 0,-14 z l 10.5,6 l 10.5,-6'
            }
        };

        this.getRawPath = function getRawPath(pathId) {
            return this.pathMap[pathId].d;
        };

        /**
         * Scales the path to the given height and width.
         * <h1>Use case</h1>
         * <p>Use case is to scale the content of elements (event, gateways) based
         * on the element bounding box's size.
         * </p>
         * <h1>Why not transform</h1>
         * <p>Scaling a path with transform() will also scale the stroke and IE does not support
         * the option 'non-scaling-stroke' to prevent this.
         * Also there are use cases where only some parts of a path should be
         * scaled.</p>
         *
         * @param {String} pathId The ID of the path.
         * @param {Object} param <p>
         *   Example param object scales the path to 60% size of the container (data.width, data.height).
         *   <pre>
         *   {
         *     xScaleFactor: 0.6,
         *     yScaleFactor:0.6,
         *     containerWidth: data.width,
         *     containerHeight: data.height,
         *     position: {
         *       mx: 0.46,
         *       my: 0.2,
         *     }
         *   }
         *   </pre>
         *   <ul>
         *    <li>targetpathwidth = xScaleFactor * containerWidth</li>
         *    <li>targetpathheight = yScaleFactor * containerHeight</li>
         *    <li>Position is used to set the starting coordinate of the path. M is computed:
         *    <ul>
         *      <li>position.x * containerWidth</li>
         *      <li>position.y * containerHeight</li>
         *    </ul>
         *    Center of the container <pre> position: {
         *       mx: 0.5,
         *       my: 0.5,
         *     }</pre>
         *     Upper left corner of the container
         *     <pre> position: {
         *       mx: 0.0,
         *       my: 0.0,
         *     }</pre>
         *    </li>
         *   </ul>
         * </p>
         *
         */
        this.getScaledPath = function getScaledPath(pathId, param) {
            var rawPath = this.pathMap[pathId];

            // positioning
            // compute the start point of the path
            var mx, my;

            if (param.abspos) {
                mx = param.abspos.x;
                my = param.abspos.y;
            } else {
                mx = param.containerWidth * param.position.mx;
                my = param.containerHeight * param.position.my;
            }

            var coordinates = {}; // map for the scaled coordinates
            if (param.position) {

                // path
                var heightRatio = (param.containerHeight / rawPath.height) * param.yScaleFactor;
                var widthRatio = (param.containerWidth / rawPath.width) * param.xScaleFactor;


                // Apply height ratio
                for (var heightIndex = 0; heightIndex < rawPath.heightElements.length; heightIndex++) {
                    coordinates['y' + heightIndex] = rawPath.heightElements[heightIndex] * heightRatio;
                }

                // Apply width ratio
                for (var widthIndex = 0; widthIndex < rawPath.widthElements.length; widthIndex++) {
                    coordinates['x' + widthIndex] = rawPath.widthElements[widthIndex] * widthRatio;
                }
            }

            // Apply value to raw path
            var path = format(
                rawPath.d, {
                    mx: mx,
                    my: my,
                    e: coordinates
                }
            );
            return path;
        };
    }

    // helpers //////////////////////

    // copied from https://github.com/adobe-webplatform/Snap.svg/blob/master/src/svg.js
    var tokenRegex = /\{([^}]+)\}/g,
        objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g; // matches .xxxxx or ["xxxxx"] to run over object properties

    function replacer(all, key, obj) {
        var res = obj;
        key.replace(objNotationRegex, function(all, name, quote, quotedName, isFunc) {
            name = name || quotedName;
            if (res) {
                if (name in res) {
                    res = res[name];
                }
                typeof res == 'function' && isFunc && (res = res());
            }
        });
        res = (res == null || res == obj ? all : res) + '';

        return res;
    }

    function format(str, obj) {
        return String(str).replace(tokenRegex, function(all, key) {
            return replacer(all, key, obj);
        });
    }

    var DrawModule$1 = {
        __init__: [ 'bpmnRenderer' ],
        bpmnRenderer: [ 'type', BpmnRenderer ],
        textRenderer: [ 'type', TextRenderer ],
        pathMap: [ 'type', PathMap ]
    };

    /**
     * A simple translation stub to be used for multi-language support
     * in diagrams. Can be easily replaced with a more sophisticated
     * solution.
     *
     * @example
     *
     * // use it inside any diagram component by injecting `translate`.
     *
     * function MyService(translate) {
     *   alert(translate('HELLO {you}', { you: 'You!' }));
     * }
     *
     * @param {String} template to interpolate
     * @param {Object} [replacements] a map with substitutes
     *
     * @return {String} the translated string
     */
    function translate$1(template, replacements) {

        replacements = replacements || {};

        return template.replace(/{([^}]+)}/g, function(_, key) {
            return replacements[key] || '{' + key + '}';
        });
    }

    var TranslateModule = {
        translate: [ 'value', translate$1 ]
    };

    var DEFAULT_LABEL_SIZE$1 = {
        width: 90,
        height: 20
    };

    var FLOW_LABEL_INDENT = 15;


    /**
     * Returns true if the given semantic has an external label
     *
     * @param {BpmnElement} semantic
     * @return {Boolean} true if has label
     */
    function isLabelExternal(semantic) {
        return is$1(semantic, 'bpmn:Event') ||
            is$1(semantic, 'bpmn:Gateway') ||
            is$1(semantic, 'bpmn:DataStoreReference') ||
            is$1(semantic, 'bpmn:DataObjectReference') ||
            is$1(semantic, 'bpmn:DataInput') ||
            is$1(semantic, 'bpmn:DataOutput') ||
            is$1(semantic, 'bpmn:SequenceFlow') ||
            is$1(semantic, 'bpmn:MessageFlow') ||
            is$1(semantic, 'bpmn:Group');
    }

    /**
     * Get the position for sequence flow labels
     *
     * @param  {Array<Point>} waypoints
     * @return {Point} the label position
     */
    function getFlowLabelPosition(waypoints) {

        // get the waypoints mid
        var mid = waypoints.length / 2 - 1;

        var first = waypoints[Math.floor(mid)];
        var second = waypoints[Math.ceil(mid + 0.01)];

        // get position
        var position = getWaypointsMid(waypoints);

        // calculate angle
        var angle = Math.atan((second.y - first.y) / (second.x - first.x));

        var x = position.x,
            y = position.y;

        if (Math.abs(angle) < Math.PI / 2) {
            y -= FLOW_LABEL_INDENT;
        } else {
            x += FLOW_LABEL_INDENT;
        }

        return { x: x, y: y };
    }


    /**
     * Get the middle of a number of waypoints
     *
     * @param  {Array<Point>} waypoints
     * @return {Point} the mid point
     */
    function getWaypointsMid(waypoints) {

        var mid = waypoints.length / 2 - 1;

        var first = waypoints[Math.floor(mid)];
        var second = waypoints[Math.ceil(mid + 0.01)];

        return {
            x: first.x + (second.x - first.x) / 2,
            y: first.y + (second.y - first.y) / 2
        };
    }


    function getExternalLabelMid(element) {

        if (element.waypoints) {
            return getFlowLabelPosition(element.waypoints);
        } else if (is$1(element, 'bpmn:Group')) {
            return {
                x: element.x + element.width / 2,
                y: element.y + DEFAULT_LABEL_SIZE$1.height / 2
            };
        } else {
            return {
                x: element.x + element.width / 2,
                y: element.y + element.height + DEFAULT_LABEL_SIZE$1.height / 2
            };
        }
    }


    /**
     * Returns the bounds of an elements label, parsed from the elements DI or
     * generated from its bounds.
     *
     * @param {BpmnElement} semantic
     * @param {djs.model.Base} element
     */
    function getExternalLabelBounds(semantic, element) {

        var mid,
            size,
            bounds,
            di = semantic.di,
            label = di.label;

        if (label && label.bounds) {
            bounds = label.bounds;

            size = {
                width: Math.max(DEFAULT_LABEL_SIZE$1.width, bounds.width),
                height: bounds.height
            };

            mid = {
                x: bounds.x + bounds.width / 2,
                y: bounds.y + bounds.height / 2
            };
        } else {

            mid = getExternalLabelMid(element);

            size = DEFAULT_LABEL_SIZE$1;
        }

        return assign({
            x: mid.x - size.width / 2,
            y: mid.y - size.height / 2
        }, size);
    }

    /**
     * This file contains portions that got extraced from Snap.svg (licensed Apache-2.0).
     *
     * @see https://github.com/adobe-webplatform/Snap.svg/blob/master/src/path.js
     */

    /* eslint no-fallthrough: "off" */

    var math = Math,
        PI = math.PI;

    function roundPoint(point) {

        return {
            x: Math.round(point.x),
            y: Math.round(point.y)
        };
    }


    /**
     * Get the mid of the given bounds or point.
     *
     * @param {Bounds|Point} bounds
     *
     * @return {Point}
     */
    function getMid(bounds) {
        return roundPoint({
            x: bounds.x + (bounds.width || 0) / 2,
            y: bounds.y + (bounds.height || 0) / 2
        });
    }

    function elementData(semantic, attrs) {
        return assign({
            id: semantic.id,
            type: semantic.$type,
            businessObject: semantic
        }, attrs);
    }

    function getWaypoints(bo, source, target) {

        var waypoints = bo.di.waypoint;

        if (!waypoints || waypoints.length < 2) {
            return [ getMid(source), getMid(target) ];
        }

        return waypoints.map(function(p) {
            return { x: p.x, y: p.y };
        });
    }

    function notYetDrawn(translate, semantic, refSemantic, property) {
        return new Error(translate('element {element} referenced by {referenced}#{property} not yet drawn', {
            element: elementToString(refSemantic),
            referenced: elementToString(semantic),
            property: property
        }));
    }


    /**
     * An importer that adds bpmn elements to the canvas
     *
     * @param {EventBus} eventBus
     * @param {Canvas} canvas
     * @param {ElementFactory} elementFactory
     * @param {ElementRegistry} elementRegistry
     * @param {Function} translate
     * @param {TextRenderer} textRenderer
     */
    function BpmnImporter(
        eventBus, canvas, elementFactory,
        elementRegistry, translate, textRenderer) {

        this._eventBus = eventBus;
        this._canvas = canvas;
        this._elementFactory = elementFactory;
        this._elementRegistry = elementRegistry;
        this._translate = translate;
        this._textRenderer = textRenderer;
    }

    BpmnImporter.$inject = [
        'eventBus',
        'canvas',
        'elementFactory',
        'elementRegistry',
        'translate',
        'textRenderer'
    ];


    /**
     * Add bpmn element (semantic) to the canvas onto the
     * specified parent shape.
     */
    BpmnImporter.prototype.add = function(semantic, parentElement) {

        var di = semantic.di,
            element,
            translate = this._translate,
            hidden;

        var parentIndex;

        // ROOT ELEMENT
        // handle the special case that we deal with a
        // invisible root element (process or collaboration)
        if (is$1(di, 'bpmndi:BPMNPlane')) {

            // add a virtual element (not being drawn)
            element = this._elementFactory.createRoot(elementData(semantic));

            this._canvas.setRootElement(element);
        }

        // SHAPE
        else if (is$1(di, 'bpmndi:BPMNShape')) {

            var collapsed = !isExpanded(semantic);
            hidden = parentElement && (parentElement.hidden || parentElement.collapsed);

            var bounds = semantic.di.bounds;

            element = this._elementFactory.createShape(elementData(semantic, {
                collapsed: collapsed,
                hidden: hidden,
                x: Math.round(bounds.x),
                y: Math.round(bounds.y),
                width: Math.round(bounds.width),
                height: Math.round(bounds.height)
            }));

            if (is$1(semantic, 'bpmn:BoundaryEvent')) {
                this._attachBoundary(semantic, element);
            }

            // insert lanes behind other flow nodes (cf. #727)
            if (is$1(semantic, 'bpmn:Lane')) {
                parentIndex = 0;
            }

            if (is$1(semantic, 'bpmn:DataStoreReference')) {

                // check wether data store is inside our outside of its semantic parent
                if (!isPointInsideBBox(parentElement, getMid(bounds))) {
                    parentElement = this._canvas.getRootElement();
                }
            }

            this._canvas.addShape(element, parentElement, parentIndex);
        }

        // CONNECTION
        else if (is$1(di, 'bpmndi:BPMNEdge')) {

            var source = this._getSource(semantic),
                target = this._getTarget(semantic);

            hidden = parentElement && (parentElement.hidden || parentElement.collapsed);

            element = this._elementFactory.createConnection(elementData(semantic, {
                hidden: hidden,
                source: source,
                target: target,
                waypoints: getWaypoints(semantic, source, target)
            }));

            if (is$1(semantic, 'bpmn:DataAssociation')) {

                // render always on top; this ensures DataAssociations
                // are rendered correctly across different "hacks" people
                // love to model such as cross participant / sub process
                // associations
                parentElement = null;
            }

            // insert sequence flows behind other flow nodes (cf. #727)
            if (is$1(semantic, 'bpmn:SequenceFlow')) {
                parentIndex = 0;
            }

            this._canvas.addConnection(element, parentElement, parentIndex);
        } else {
            throw new Error(translate('unknown di {di} for element {semantic}', {
                di: elementToString(di),
                semantic: elementToString(semantic)
            }));
        }
        // (optional) LABEL
        if (isLabelExternal(semantic) && getLabel(element)) {
            this.addLabel(semantic, element);
        }


        this._eventBus.fire('bpmnElement.added', { element: element });

        return element;
    };


    /**
     * Attach the boundary element to the given host
     *
     * @param {ModdleElement} boundarySemantic
     * @param {djs.model.Base} boundaryElement
     */
    BpmnImporter.prototype._attachBoundary = function(boundarySemantic, boundaryElement) {
        var translate = this._translate;
        var hostSemantic = boundarySemantic.attachedToRef;

        if (!hostSemantic) {
            throw new Error(translate('missing {semantic}#attachedToRef', {
                semantic: elementToString(boundarySemantic)
            }));
        }

        var host = this._elementRegistry.get(hostSemantic.id),
            attachers = host && host.attachers;

        if (!host) {
            throw notYetDrawn(translate, boundarySemantic, hostSemantic, 'attachedToRef');
        }

        // wire element.host <> host.attachers
        boundaryElement.host = host;

        if (!attachers) {
            host.attachers = attachers = [];
        }

        if (attachers.indexOf(boundaryElement) === -1) {
            attachers.push(boundaryElement);
        }
    };


    /**
     * add label for an element
     */
    BpmnImporter.prototype.addLabel = function(semantic, element) {
        var bounds,
            text,
            label;

        bounds = getExternalLabelBounds(semantic, element);

        text = getLabel(element);

        if (text) {
            // get corrected bounds from actual layouted text
            bounds = this._textRenderer.getExternalLabelBounds(bounds, text);
        }

        label = this._elementFactory.createLabel(elementData(semantic, {
            id: semantic.id + '_label',
            labelTarget: element,
            type: 'label',
            hidden: element.hidden || !getLabel(element),
            x: Math.round(bounds.x),
            y: Math.round(bounds.y),
            width: Math.round(bounds.width),
            height: Math.round(bounds.height)
        }));

        return this._canvas.addShape(label, element.parent);
    };

    /**
     * Return the drawn connection end based on the given side.
     *
     * @throws {Error} if the end is not yet drawn
     */
    BpmnImporter.prototype._getEnd = function(semantic, side) {

        var element,
            refSemantic,
            type = semantic.$type,
            translate = this._translate;

        refSemantic = semantic[side + 'Ref'];

        // handle mysterious isMany DataAssociation#sourceRef
        if (side === 'source' && type === 'bpmn:DataInputAssociation') {
            refSemantic = refSemantic && refSemantic[0];
        }

        // fix source / target for DataInputAssociation / DataOutputAssociation
        if (side === 'source' && type === 'bpmn:DataOutputAssociation' ||
            side === 'target' && type === 'bpmn:DataInputAssociation') {

            refSemantic = semantic.$parent;
        }

        element = refSemantic && this._getElement(refSemantic);

        if (element) {
            return element;
        }

        if (refSemantic) {
            throw notYetDrawn(translate, semantic, refSemantic, side + 'Ref');
        } else {
            throw new Error(translate('{semantic}#{side} Ref not specified', {
                semantic: elementToString(semantic),
                side: side
            }));
        }
    };

    BpmnImporter.prototype._getSource = function(semantic) {
        return this._getEnd(semantic, 'source');
    };

    BpmnImporter.prototype._getTarget = function(semantic) {
        return this._getEnd(semantic, 'target');
    };


    BpmnImporter.prototype._getElement = function(semantic) {
        return this._elementRegistry.get(semantic.id);
    };


    // helpers ////////////////////

    function isPointInsideBBox(bbox, point) {
        var x = point.x,
            y = point.y;

        return x >= bbox.x &&
            x <= bbox.x + bbox.width &&
            y >= bbox.y &&
            y <= bbox.y + bbox.height;
    }

    var ImportModule = {
        __depends__: [
            TranslateModule
        ],
        bpmnImporter: [ 'type', BpmnImporter ]
    };

    var CoreModule$1 = {
        __depends__: [
            DrawModule$1,
            ImportModule
        ]
    };

    function getOriginal(event) {
        return event.originalEvent || event.srcEvent;
    }


    function toPoint(event) {

        if (event.pointers && event.pointers.length) {
            event = event.pointers[0];
        }

        if (event.touches && event.touches.length) {
            event = event.touches[0];
        }

        return event ? {
            x: event.clientX,
            y: event.clientY
        } : null;
    }

    function isMac() {
        return (/mac/i).test(navigator.platform);
    }

    function isPrimaryButton(event) {
        // button === 0 -> left áka primary mouse button
        return !(getOriginal(event) || event).button;
    }

    function hasPrimaryModifier(event) {
        var originalEvent = getOriginal(event) || event;

        if (!isPrimaryButton(event)) {
            return false;
        }

        // Use alt as primary modifier key for mac OS
        if (isMac()) {
            return originalEvent.metaKey;
        } else {
            return originalEvent.ctrlKey;
        }
    }

    function allowAll(e) { return true; }

    var LOW_PRIORITY = 500;

    /**
     * A plugin that provides interaction events for diagram elements.
     *
     * It emits the following events:
     *
     *   * element.hover
     *   * element.out
     *   * element.click
     *   * element.dblclick
     *   * element.mousedown
     *   * element.contextmenu
     *
     * Each event is a tuple { element, gfx, originalEvent }.
     *
     * Canceling the event via Event#preventDefault()
     * prevents the original DOM operation.
     *
     * @param {EventBus} eventBus
     */
    function InteractionEvents(eventBus, elementRegistry, styles) {

        var HIT_STYLE = styles.cls('djs-hit', [ 'no-fill', 'no-border' ], {
            stroke: 'white',
            strokeWidth: 15
        });

        /**
         * Fire an interaction event.
         *
         * @param {String} type local event name, e.g. element.click.
         * @param {DOMEvent} event native event
         * @param {djs.model.Base} [element] the diagram element to emit the event on;
         *                                   defaults to the event target
         */
        function fire(type, event, element) {

            if (isIgnored(type, event)) {
                return;
            }

            var target, gfx, returnValue;

            if (!element) {
                target = event.delegateTarget || event.target;

                if (target) {
                    gfx = target;
                    element = elementRegistry.get(gfx);
                }
            } else {
                gfx = elementRegistry.getGraphics(element);
            }

            if (!gfx || !element) {
                return;
            }

            returnValue = eventBus.fire(type, {
                element: element,
                gfx: gfx,
                originalEvent: event
            });

            if (returnValue === false) {
                event.stopPropagation();
                event.preventDefault();
            }
        }

        // TODO(nikku): document this
        var handlers = {};

        function mouseHandler(localEventName) {
            return handlers[localEventName];
        }

        function isIgnored(localEventName, event) {

            var filter = ignoredFilters[localEventName] || isPrimaryButton;

            // only react on left mouse button interactions
            // except for interaction events that are enabled
            // for secundary mouse button
            return !filter(event);
        }

        var bindings = {
            mouseover: 'element.hover',
            mouseout: 'element.out',
            click: 'element.click',
            dblclick: 'element.dblclick',
            mousedown: 'element.mousedown',
            mousemove: 'element.mousemove',
            mouseup: 'element.mouseup',
            contextmenu: 'element.contextmenu'
        };

        var ignoredFilters = {
            'element.contextmenu': allowAll
        };


        // manual event trigger

        /**
         * Trigger an interaction event (based on a native dom event)
         * on the target shape or connection.
         *
         * @param {String} eventName the name of the triggered DOM event
         * @param {MouseEvent} event
         * @param {djs.model.Base} targetElement
         */
        function triggerMouseEvent(eventName, event, targetElement) {

            // i.e. element.mousedown...
            var localEventName = bindings[eventName];

            if (!localEventName) {
                throw new Error('unmapped DOM event name <' + eventName + '>');
            }

            return fire(localEventName, event, targetElement);
        }


        var elementSelector = 'svg, .djs-element';

        // event registration

        function registerEvent(node, event, localEvent, ignoredFilter) {

            var handler = handlers[localEvent] = function(event) {
                fire(localEvent, event);
            };

            if (ignoredFilter) {
                ignoredFilters[localEvent] = ignoredFilter;
            }

            handler.$delegate = delegateEvents.bind(node, elementSelector, event, handler);
        }

        function unregisterEvent(node, event, localEvent) {

            var handler = mouseHandler(localEvent);

            if (!handler) {
                return;
            }

            delegateEvents.unbind(node, event, handler.$delegate);
        }

        function registerEvents(svg) {
            forEach(bindings, function(val, key) {
                registerEvent(svg, key, val);
            });
        }

        function unregisterEvents(svg) {
            forEach(bindings, function(val, key) {
                unregisterEvent(svg, key, val);
            });
        }

        eventBus.on('canvas.destroy', function(event) {
            unregisterEvents(event.svg);
        });

        eventBus.on('canvas.init', function(event) {
            registerEvents(event.svg);
        });


        eventBus.on([ 'shape.added', 'connection.added' ], function(event) {
            var element = event.element,
                gfx = event.gfx,
                hit;

            if (element.waypoints) {
                hit = createLine(element.waypoints);
            } else {
                hit = create('rect');
                attr$1(hit, {
                    x: 0,
                    y: 0,
                    width: element.width,
                    height: element.height
                });
            }

            attr$1(hit, HIT_STYLE);

            append(gfx, hit);
        });

        // Update djs-hit on change.
        // A low priortity is necessary, because djs-hit of labels has to be updated
        // after the label bounds have been updated in the renderer.
        eventBus.on('shape.changed', LOW_PRIORITY, function(event) {

            var element = event.element,
                gfx = event.gfx,
                hit = query('.djs-hit', gfx);

            attr$1(hit, {
                width: element.width,
                height: element.height
            });
        });

        eventBus.on('connection.changed', function(event) {

            var element = event.element,
                gfx = event.gfx,
                hit = query('.djs-hit', gfx);

            updateLine(hit, element.waypoints);
        });


        // API

        this.fire = fire;

        this.triggerMouseEvent = triggerMouseEvent;

        this.mouseHandler = mouseHandler;

        this.registerEvent = registerEvent;
        this.unregisterEvent = unregisterEvent;
    }


    InteractionEvents.$inject = [
        'eventBus',
        'elementRegistry',
        'styles'
    ];


    /**
     * An event indicating that the mouse hovered over an element
     *
     * @event element.hover
     *
     * @type {Object}
     * @property {djs.model.Base} element
     * @property {SVGElement} gfx
     * @property {Event} originalEvent
     */

    /**
     * An event indicating that the mouse has left an element
     *
     * @event element.out
     *
     * @type {Object}
     * @property {djs.model.Base} element
     * @property {SVGElement} gfx
     * @property {Event} originalEvent
     */

    /**
     * An event indicating that the mouse has clicked an element
     *
     * @event element.click
     *
     * @type {Object}
     * @property {djs.model.Base} element
     * @property {SVGElement} gfx
     * @property {Event} originalEvent
     */

    /**
     * An event indicating that the mouse has double clicked an element
     *
     * @event element.dblclick
     *
     * @type {Object}
     * @property {djs.model.Base} element
     * @property {SVGElement} gfx
     * @property {Event} originalEvent
     */

    /**
     * An event indicating that the mouse has gone down on an element.
     *
     * @event element.mousedown
     *
     * @type {Object}
     * @property {djs.model.Base} element
     * @property {SVGElement} gfx
     * @property {Event} originalEvent
     */

    /**
     * An event indicating that the mouse has gone up on an element.
     *
     * @event element.mouseup
     *
     * @type {Object}
     * @property {djs.model.Base} element
     * @property {SVGElement} gfx
     * @property {Event} originalEvent
     */

    /**
     * An event indicating that the context menu action is triggered
     * via mouse or touch controls.
     *
     * @event element.contextmenu
     *
     * @type {Object}
     * @property {djs.model.Base} element
     * @property {SVGElement} gfx
     * @property {Event} originalEvent
     */

    var InteractionEventsModule = {
        __init__: [ 'interactionEvents' ],
        interactionEvents: [ 'type', InteractionEvents ]
    };

    var LOW_PRIORITY$1 = 500;


    /**
     * @class
     *
     * A plugin that adds an outline to shapes and connections that may be activated and styled
     * via CSS classes.
     *
     * @param {EventBus} eventBus
     * @param {Styles} styles
     * @param {ElementRegistry} elementRegistry
     */
    function Outline(eventBus, styles, elementRegistry) {

        this.offset = 6;

        var OUTLINE_STYLE = styles.cls('djs-outline', [ 'no-fill' ]);

        var self = this;

        function createOutline(gfx, bounds) {
            var outline = create('rect');

            attr$1(outline, assign({
                x: 10,
                y: 10,
                width: 100,
                height: 100
            }, OUTLINE_STYLE));

            append(gfx, outline);

            return outline;
        }

        // A low priortity is necessary, because outlines of labels have to be updated
        // after the label bounds have been updated in the renderer.
        eventBus.on([ 'shape.added', 'shape.changed' ], LOW_PRIORITY$1, function(event) {
            var element = event.element,
                gfx = event.gfx;

            var outline = query('.djs-outline', gfx);

            if (!outline) {
                outline = createOutline(gfx);
            }

            self.updateShapeOutline(outline, element);
        });

        eventBus.on([ 'connection.added', 'connection.changed' ], function(event) {
            var element = event.element,
                gfx = event.gfx;

            var outline = query('.djs-outline', gfx);

            if (!outline) {
                outline = createOutline(gfx);
            }

            self.updateConnectionOutline(outline, element);
        });
    }


    /**
     * Updates the outline of a shape respecting the dimension of the
     * element and an outline offset.
     *
     * @param  {SVGElement} outline
     * @param  {djs.model.Base} element
     */
    Outline.prototype.updateShapeOutline = function(outline, element) {

        attr$1(outline, {
            x: -this.offset,
            y: -this.offset,
            width: element.width + this.offset * 2,
            height: element.height + this.offset * 2
        });

    };


    /**
     * Updates the outline of a connection respecting the bounding box of
     * the connection and an outline offset.
     *
     * @param  {SVGElement} outline
     * @param  {djs.model.Base} element
     */
    Outline.prototype.updateConnectionOutline = function(outline, connection) {

        var bbox = getBBox(connection);

        attr$1(outline, {
            x: bbox.x - this.offset,
            y: bbox.y - this.offset,
            width: bbox.width + this.offset * 2,
            height: bbox.height + this.offset * 2
        });

    };


    Outline.$inject = ['eventBus', 'styles', 'elementRegistry'];

    var OutlineModule = {
        __init__: [ 'outline' ],
        outline: [ 'type', Outline ]
    };

    /**
     * A service that offers the current selection in a diagram.
     * Offers the api to control the selection, too.
     *
     * @class
     *
     * @param {EventBus} eventBus the event bus
     */
    function Selection(eventBus) {

        this._eventBus = eventBus;

        this._selectedElements = [];

        var self = this;

        eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) {
            var element = e.element;
            self.deselect(element);
        });

        eventBus.on([ 'diagram.clear' ], function(e) {
            self.select(null);
        });
    }

    Selection.$inject = [ 'eventBus' ];


    Selection.prototype.deselect = function(element) {
        var selectedElements = this._selectedElements;

        var idx = selectedElements.indexOf(element);

        if (idx !== -1) {
            var oldSelection = selectedElements.slice();

            selectedElements.splice(idx, 1);

            this._eventBus.fire('selection.changed', { oldSelection: oldSelection, newSelection: selectedElements });
        }
    };


    Selection.prototype.get = function() {
        return this._selectedElements;
    };

    Selection.prototype.isSelected = function(element) {
        return this._selectedElements.indexOf(element) !== -1;
    };


    /**
     * This method selects one or more elements on the diagram.
     *
     * By passing an additional add parameter you can decide whether or not the element(s)
     * should be added to the already existing selection or not.
     *
     * @method Selection#select
     *
     * @param  {Object|Object[]} elements element or array of elements to be selected
     * @param  {boolean} [add] whether the element(s) should be appended to the current selection, defaults to false
     */
    Selection.prototype.select = function(elements, add) {
        var selectedElements = this._selectedElements,
            oldSelection = selectedElements.slice();

        if (!isArray(elements)) {
            elements = elements ? [ elements ] : [];
        }

        // selection may be cleared by passing an empty array or null
        // to the method
        if (add) {
            forEach(elements, function(element) {
                if (selectedElements.indexOf(element) !== -1) {
                    // already selected
                    return;
                } else {
                    selectedElements.push(element);
                }
            });
        } else {
            this._selectedElements = selectedElements = elements.slice();
        }

        this._eventBus.fire('selection.changed', { oldSelection: oldSelection, newSelection: selectedElements });
    };

    var MARKER_HOVER = 'hover',
        MARKER_SELECTED = 'selected';


    /**
     * A plugin that adds a visible selection UI to shapes and connections
     * by appending the <code>hover</code> and <code>selected</code> classes to them.
     *
     * @class
     *
     * Makes elements selectable, too.
     *
     * @param {EventBus} events
     * @param {SelectionService} selection
     * @param {Canvas} canvas
     */
    function SelectionVisuals(events, canvas, selection, styles) {

        this._multiSelectionBox = null;

        function addMarker(e, cls) {
            canvas.addMarker(e, cls);
        }

        function removeMarker(e, cls) {
            canvas.removeMarker(e, cls);
        }

        events.on('element.hover', function(event) {
            addMarker(event.element, MARKER_HOVER);
        });

        events.on('element.out', function(event) {
            removeMarker(event.element, MARKER_HOVER);
        });

        events.on('selection.changed', function(event) {

            function deselect(s) {
                removeMarker(s, MARKER_SELECTED);
            }

            function select(s) {
                addMarker(s, MARKER_SELECTED);
            }

            var oldSelection = event.oldSelection,
                newSelection = event.newSelection;

            forEach(oldSelection, function(e) {
                if (newSelection.indexOf(e) === -1) {
                    deselect(e);
                }
            });

            forEach(newSelection, function(e) {
                if (oldSelection.indexOf(e) === -1) {
                    select(e);
                }
            });
        });
    }

    SelectionVisuals.$inject = [
        'eventBus',
        'canvas',
        'selection',
        'styles'
    ];

    function SelectionBehavior(
        eventBus, selection, canvas,
        elementRegistry) {

        eventBus.on('create.end', 500, function(e) {

            // select the created shape after a
            // successful create operation
            if (e.context.canExecute) {
                selection.select(e.context.shape);
            }
        });

        eventBus.on('connect.end', 500, function(e) {

            // select the connect end target
            // after a connect operation
            if (e.context.canExecute && e.context.target) {
                selection.select(e.context.target);
            }
        });

        eventBus.on('shape.move.end', 500, function(e) {
            var previousSelection = e.previousSelection || [];

            var shape = elementRegistry.get(e.context.shape.id);

            // make sure at least the main moved element is being
            // selected after a move operation
            var inSelection = find(previousSelection, function(selectedShape) {
                return shape.id === selectedShape.id;
            });

            if (!inSelection) {
                selection.select(shape);
            }
        });

        // Shift + click selection
        eventBus.on('element.click', function(event) {

            var element = event.element;

            // do not select the root element
            // or connections
            if (element === canvas.getRootElement()) {
                element = null;
            }

            var isSelected = selection.isSelected(element),
                isMultiSelect = selection.get().length > 1;

            // mouse-event: SELECTION_KEY
            var add = hasPrimaryModifier(event);

            // select OR deselect element in multi selection
            if (isSelected && isMultiSelect) {
                if (add) {
                    return selection.deselect(element);
                } else {
                    return selection.select(element);
                }
            } else
            if (!isSelected) {
                selection.select(element, add);
            } else {
                selection.deselect(element);
            }
        });
    }

    SelectionBehavior.$inject = [
        'eventBus',
        'selection',
        'canvas',
        'elementRegistry'
    ];

    var SelectionModule = {
        __init__: [ 'selectionVisuals', 'selectionBehavior' ],
        __depends__: [
            InteractionEventsModule,
            OutlineModule
        ],
        selection: [ 'type', Selection ],
        selectionVisuals: [ 'type', SelectionVisuals ],
        selectionBehavior: [ 'type', SelectionBehavior ]
    };

    /**
     * Util that provides unique IDs.
     *
     * @class djs.util.IdGenerator
     * @constructor
     * @memberOf djs.util
     *
     * The ids can be customized via a given prefix and contain a random value to avoid collisions.
     *
     * @param {String} prefix a prefix to prepend to generated ids (for better readability)
     */
    function IdGenerator(prefix) {

        this._counter = 0;
        this._prefix = (prefix ? prefix + '-' : '') + Math.floor(Math.random() * 1000000000) + '-';
    }

    /**
     * Returns a next unique ID.
     *
     * @method djs.util.IdGenerator#next
     *
     * @returns {String} the id
     */
    IdGenerator.prototype.next = function() {
        return this._prefix + (++this._counter);
    };

    // document wide unique overlay ids
    var ids = new IdGenerator('ov');

    var LOW_PRIORITY$2 = 500;


    /**
     * A service that allows users to attach overlays to diagram elements.
     *
     * The overlay service will take care of overlay positioning during updates.
     *
     * @example
     *
     * // add a pink badge on the top left of the shape
     * overlays.add(someShape, {
     *   position: {
     *     top: -5,
     *     left: -5
     *   },
     *   html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
     * });
     *
     * // or add via shape id
     *
     * overlays.add('some-element-id', {
     *   position: {
     *     top: -5,
     *     left: -5
     *   }
     *   html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
     * });
     *
     * // or add with optional type
     *
     * overlays.add(someShape, 'badge', {
     *   position: {
     *     top: -5,
     *     left: -5
     *   }
     *   html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
     * });
     *
     *
     * // remove an overlay
     *
     * var id = overlays.add(...);
     * overlays.remove(id);
     *
     *
     * You may configure overlay defaults during tool by providing a `config` module
     * with `overlays.defaults` as an entry:
     *
     * {
     *   overlays: {
     *     defaults: {
     *       show: {
     *         minZoom: 0.7,
     *         maxZoom: 5.0
     *       },
     *       scale: {
     *         min: 1
     *       }
     *     }
     * }
     *
     * @param {Object} config
     * @param {EventBus} eventBus
     * @param {Canvas} canvas
     * @param {ElementRegistry} elementRegistry
     */
    function Overlays(config, eventBus, canvas, elementRegistry) {

        this._eventBus = eventBus;
        this._canvas = canvas;
        this._elementRegistry = elementRegistry;

        this._ids = ids;

        this._overlayDefaults = assign({
            // no show constraints
            show: null,

            // always scale
            scale: true
        }, config && config.defaults);

        /**
         * Mapping overlayId -> overlay
         */
        this._overlays = {};

        /**
         * Mapping elementId -> overlay container
         */
        this._overlayContainers = [];

        // root html element for all overlays
        this._overlayRoot = createRoot(canvas.getContainer());

        this._init();
    }


    Overlays.$inject = [
        'config.overlays',
        'eventBus',
        'canvas',
        'elementRegistry'
    ];


    /**
     * Returns the overlay with the specified id or a list of overlays
     * for an element with a given type.
     *
     * @example
     *
     * // return the single overlay with the given id
     * overlays.get('some-id');
     *
     * // return all overlays for the shape
     * overlays.get({ element: someShape });
     *
     * // return all overlays on shape with type 'badge'
     * overlays.get({ element: someShape, type: 'badge' });
     *
     * // shape can also be specified as id
     * overlays.get({ element: 'element-id', type: 'badge' });
     *
     *
     * @param {Object} search
     * @param {String} [search.id]
     * @param {String|djs.model.Base} [search.element]
     * @param {String} [search.type]
     *
     * @return {Object|Array<Object>} the overlay(s)
     */
    Overlays.prototype.get = function(search) {

        if (isString(search)) {
            search = { id: search };
        }

        if (isString(search.element)) {
            search.element = this._elementRegistry.get(search.element);
        }

        if (search.element) {
            var container = this._getOverlayContainer(search.element, true);

            // return a list of overlays when searching by element (+type)
            if (container) {
                return search.type ? filter(container.overlays, matchPattern({ type: search.type })) : container.overlays.slice();
            } else {
                return [];
            }
        } else
        if (search.type) {
            return filter(this._overlays, matchPattern({ type: search.type }));
        } else {
            // return single element when searching by id
            return search.id ? this._overlays[search.id] : null;
        }
    };

    /**
     * Adds a HTML overlay to an element.
     *
     * @param {String|djs.model.Base}   element   attach overlay to this shape
     * @param {String}                  [type]    optional type to assign to the overlay
     * @param {Object}                  overlay   the overlay configuration
     *
     * @param {String|DOMElement}       overlay.html                 html element to use as an overlay
     * @param {Object}                  [overlay.show]               show configuration
     * @param {Number}                  [overlay.show.minZoom]       minimal zoom level to show the overlay
     * @param {Number}                  [overlay.show.maxZoom]       maximum zoom level to show the overlay
     * @param {Object}                  overlay.position             where to attach the overlay
     * @param {Number}                  [overlay.position.left]      relative to element bbox left attachment
     * @param {Number}                  [overlay.position.top]       relative to element bbox top attachment
     * @param {Number}                  [overlay.position.bottom]    relative to element bbox bottom attachment
     * @param {Number}                  [overlay.position.right]     relative to element bbox right attachment
     * @param {Boolean|Object}          [overlay.scale=true]         false to preserve the same size regardless of
     *                                                               diagram zoom
     * @param {Number}                  [overlay.scale.min]
     * @param {Number}                  [overlay.scale.max]
     *
     * @return {String}                 id that may be used to reference the overlay for update or removal
     */
    Overlays.prototype.add = function(element, type, overlay) {

        if (isObject(type)) {
            overlay = type;
            type = null;
        }

        if (!element.id) {
            element = this._elementRegistry.get(element);
        }

        if (!overlay.position) {
            throw new Error('must specifiy overlay position');
        }

        if (!overlay.html) {
            throw new Error('must specifiy overlay html');
        }

        if (!element) {
            throw new Error('invalid element specified');
        }

        var id = this._ids.next();

        overlay = assign({}, this._overlayDefaults, overlay, {
            id: id,
            type: type,
            element: element,
            html: overlay.html
        });

        this._addOverlay(overlay);

        return id;
    };


    /**
     * Remove an overlay with the given id or all overlays matching the given filter.
     *
     * @see Overlays#get for filter options.
     *
     * @param {String} [id]
     * @param {Object} [filter]
     */
    Overlays.prototype.remove = function(filter) {

        var overlays = this.get(filter) || [];

        if (!isArray(overlays)) {
            overlays = [ overlays ];
        }

        var self = this;

        forEach(overlays, function(overlay) {

            var container = self._getOverlayContainer(overlay.element, true);

            if (overlay) {
                remove(overlay.html);
                remove(overlay.htmlContainer);

                delete overlay.htmlContainer;
                delete overlay.element;

                delete self._overlays[overlay.id];
            }

            if (container) {
                var idx = container.overlays.indexOf(overlay);
                if (idx !== -1) {
                    container.overlays.splice(idx, 1);
                }
            }
        });

    };


    Overlays.prototype.show = function() {
        setVisible(this._overlayRoot);
    };


    Overlays.prototype.hide = function() {
        setVisible(this._overlayRoot, false);
    };

    Overlays.prototype.clear = function() {
        this._overlays = {};

        this._overlayContainers = [];

        clear(this._overlayRoot);
    };

    Overlays.prototype._updateOverlayContainer = function(container) {
        var element = container.element,
            html = container.html;

        // update container left,top according to the elements x,y coordinates
        // this ensures we can attach child elements relative to this container

        var x = element.x,
            y = element.y;

        if (element.waypoints) {
            var bbox = getBBox(element);
            x = bbox.x;
            y = bbox.y;
        }

        setPosition(html, x, y);

        attr(container.html, 'data-container-id', element.id);
    };


    Overlays.prototype._updateOverlay = function(overlay) {

        var position = overlay.position,
            htmlContainer = overlay.htmlContainer,
            element = overlay.element;

        // update overlay html relative to shape because
        // it is already positioned on the element

        // update relative
        var left = position.left,
            top = position.top;

        if (position.right !== undefined) {

            var width;

            if (element.waypoints) {
                width = getBBox(element).width;
            } else {
                width = element.width;
            }

            left = position.right * -1 + width;
        }

        if (position.bottom !== undefined) {

            var height;

            if (element.waypoints) {
                height = getBBox(element).height;
            } else {
                height = element.height;
            }

            top = position.bottom * -1 + height;
        }

        setPosition(htmlContainer, left || 0, top || 0);
    };


    Overlays.prototype._createOverlayContainer = function(element) {
        var html = domify('<div class="djs-overlays" style="position: absolute" />');

        this._overlayRoot.appendChild(html);

        var container = {
            html: html,
            element: element,
            overlays: []
        };

        this._updateOverlayContainer(container);

        this._overlayContainers.push(container);

        return container;
    };


    Overlays.prototype._updateRoot = function(viewbox) {
        var scale = viewbox.scale || 1;

        var matrix = 'matrix(' +
            [
                scale,
                0,
                0,
                scale,
                -1 * viewbox.x * scale,
                -1 * viewbox.y * scale
            ].join(',') +
            ')';

        setTransform(this._overlayRoot, matrix);
    };


    Overlays.prototype._getOverlayContainer = function(element, raw) {
        var container = find(this._overlayContainers, function(c) {
            return c.element === element;
        });


        if (!container && !raw) {
            return this._createOverlayContainer(element);
        }

        return container;
    };


    Overlays.prototype._addOverlay = function(overlay) {

        var id = overlay.id,
            element = overlay.element,
            html = overlay.html,
            htmlContainer,
            overlayContainer;

        // unwrap jquery (for those who need it)
        if (html.get && html.constructor.prototype.jquery) {
            html = html.get(0);
        }

        // create proper html elements from
        // overlay HTML strings
        if (isString(html)) {
            html = domify(html);
        }

        overlayContainer = this._getOverlayContainer(element);

        htmlContainer = domify('<div class="djs-overlay" data-overlay-id="' + id + '" style="position: absolute">');

        htmlContainer.appendChild(html);

        if (overlay.type) {
            classes(htmlContainer).add('djs-overlay-' + overlay.type);
        }

        overlay.htmlContainer = htmlContainer;

        overlayContainer.overlays.push(overlay);
        overlayContainer.html.appendChild(htmlContainer);

        this._overlays[id] = overlay;

        this._updateOverlay(overlay);
        this._updateOverlayVisibilty(overlay, this._canvas.viewbox());
    };


    Overlays.prototype._updateOverlayVisibilty = function(overlay, viewbox) {
        var show = overlay.show,
            minZoom = show && show.minZoom,
            maxZoom = show && show.maxZoom,
            htmlContainer = overlay.htmlContainer,
            visible = true;

        if (show) {
            if (
                (isDefined(minZoom) && minZoom > viewbox.scale) ||
                (isDefined(maxZoom) && maxZoom < viewbox.scale)
            ) {
                visible = false;
            }

            setVisible(htmlContainer, visible);
        }

        this._updateOverlayScale(overlay, viewbox);
    };


    Overlays.prototype._updateOverlayScale = function(overlay, viewbox) {
        var shouldScale = overlay.scale,
            minScale,
            maxScale,
            htmlContainer = overlay.htmlContainer;

        var scale, transform = '';

        if (shouldScale !== true) {

            if (shouldScale === false) {
                minScale = 1;
                maxScale = 1;
            } else {
                minScale = shouldScale.min;
                maxScale = shouldScale.max;
            }

            if (isDefined(minScale) && viewbox.scale < minScale) {
                scale = (1 / viewbox.scale || 1) * minScale;
            }

            if (isDefined(maxScale) && viewbox.scale > maxScale) {
                scale = (1 / viewbox.scale || 1) * maxScale;
            }
        }

        if (isDefined(scale)) {
            transform = 'scale(' + scale + ',' + scale + ')';
        }

        setTransform(htmlContainer, transform);
    };


    Overlays.prototype._updateOverlaysVisibilty = function(viewbox) {

        var self = this;

        forEach(this._overlays, function(overlay) {
            self._updateOverlayVisibilty(overlay, viewbox);
        });
    };


    Overlays.prototype._init = function() {

        var eventBus = this._eventBus;

        var self = this;


        // scroll/zoom integration

        function updateViewbox(viewbox) {
            self._updateRoot(viewbox);
            self._updateOverlaysVisibilty(viewbox);

            self.show();
        }

        eventBus.on('canvas.viewbox.changing', function(event) {
            self.hide();
        });

        eventBus.on('canvas.viewbox.changed', function(event) {
            updateViewbox(event.viewbox);
        });


        // remove integration

        eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) {
            var element = e.element;
            var overlays = self.get({ element: element });

            forEach(overlays, function(o) {
                self.remove(o.id);
            });

            var container = self._getOverlayContainer(element);

            if (container) {
                remove(container.html);
                var i = self._overlayContainers.indexOf(container);
                if (i !== -1) {
                    self._overlayContainers.splice(i, 1);
                }
            }
        });


        // move integration

        eventBus.on('element.changed', LOW_PRIORITY$2, function(e) {
            var element = e.element;

            var container = self._getOverlayContainer(element, true);

            if (container) {
                forEach(container.overlays, function(overlay) {
                    self._updateOverlay(overlay);
                });

                self._updateOverlayContainer(container);
            }
        });


        // marker integration, simply add them on the overlays as classes, too.

        eventBus.on('element.marker.update', function(e) {
            var container = self._getOverlayContainer(e.element, true);
            if (container) {
                classes(container.html)[e.add ? 'add' : 'remove'](e.marker);
            }
        });


        // clear overlays with diagram

        eventBus.on('diagram.clear', this.clear, this);
    };



    // helpers /////////////////////////////

    function createRoot(parentNode) {
        var root = domify(
            '<div class="djs-overlay-container" style="position: absolute; width: 0; height: 0;" />'
        );

        parentNode.insertBefore(root, parentNode.firstChild);

        return root;
    }

    function setPosition(el, x, y) {
        assign(el.style, { left: x + 'px', top: y + 'px' });
    }

    function setVisible(el, visible) {
        el.style.display = visible === false ? 'none' : '';
    }

    function setTransform(el, transform) {

        el.style['transform-origin'] = 'top left';

        [ '', '-ms-', '-webkit-' ].forEach(function(prefix) {
            el.style[prefix + 'transform'] = transform;
        });
    }

    var OverlaysModule = {
        __init__: [ 'overlays' ],
        overlays: [ 'type', Overlays ]
    };

    /**
     * This file must not be changed or exchanged.
     *
     * @see http://bpmn.io/license for more information.
     */


        // inlined ../../resources/logo.svg
    var BPMNIO_LOGO_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 960"><path fill="#fff" d="M960 60v839c0 33-27 61-60 61H60c-33 0-60-27-60-60V60C0 27 27 0 60 0h839c34 0 61 27 61 60z"/><path fill="#52b415" d="M217 548a205 205 0 0 0-144 58 202 202 0 0 0-4 286 202 202 0 0 0 285 3 200 200 0 0 0 48-219 203 203 0 0 0-185-128zM752 6a206 206 0 0 0-192 285 206 206 0 0 0 269 111 207 207 0 0 0 111-260A204 204 0 0 0 752 6zM62 0A62 62 0 0 0 0 62v398l60 46a259 259 0 0 1 89-36c5-28 10-57 14-85l99 2 12 85a246 246 0 0 1 88 38l70-52 69 71-52 68c17 30 29 58 35 90l86 14-2 100-86 12a240 240 0 0 1-38 89l43 58h413c37 0 60-27 60-61V407a220 220 0 0 1-44 40l21 85-93 39-45-76a258 258 0 0 1-98 1l-45 76-94-39 22-85a298 298 0 0 1-70-69l-86 22-38-94 76-45a258 258 0 0 1-1-98l-76-45 40-94 85 22a271 271 0 0 1 41-47z"/></svg>';

    var BPMNIO_LOGO_URL = 'data:image/svg+xml,' + encodeURIComponent(BPMNIO_LOGO_SVG);

    var BPMNIO_IMG = '<img width="52" height="52" src="' + BPMNIO_LOGO_URL + '" />';

    function css(attrs) {
        return attrs.join(';');
    }

    var LIGHTBOX_STYLES = css([
        'z-index: 1001',
        'position: fixed',
        'top: 0',
        'left: 0',
        'right: 0',
        'bottom: 0'
    ]);

    var BACKDROP_STYLES = css([
        'width: 100%',
        'height: 100%',
        'background: rgba(0,0,0,0.2)'
    ]);

    var NOTICE_STYLES = css([
        'position: absolute',
        'left: 50%',
        'top: 40%',
        'margin: 0 -130px',
        'width: 260px',
        'padding: 10px',
        'background: white',
        'border: solid 1px #AAA',
        'border-radius: 3px',
        'font-family: Helvetica, Arial, sans-serif',
        'font-size: 14px',
        'line-height: 1.2em'
    ]);

    var LIGHTBOX_MARKUP =
        '<div class="bjs-powered-by-lightbox" style="' + LIGHTBOX_STYLES + '">' +
        '<div class="backdrop" style="' + BACKDROP_STYLES + '"></div>' +
        '<div class="notice" style="' + NOTICE_STYLES + '">' +
        '<a href="http://bpmn.io" target="_blank" style="float: left; margin-right: 10px">' +
        BPMNIO_IMG +
        '</a>' +
        'Web-based tooling for BPMN, DMN and CMMN diagrams ' +
        'powered by <a href="http://bpmn.io" target="_blank">bpmn.io</a>.' +
        '</div>' +
        '</div>';


    var lightbox;

    function open() {

        if (!lightbox) {
            // lightbox = domify(LIGHTBOX_MARKUP);
            //
            // delegateEvents.bind(lightbox, '.backdrop', 'click', function(event) {
            //     document.body.removeChild(lightbox);
            // });
        }

        // document.body.appendChild(lightbox);
    }

    /**
     * The code in the <project-logo></project-logo> area
     * must not be changed.
     *
     * @see http://bpmn.io/license for more information.
     */


    function checkValidationError(err) {

        // check if we can help the user by indicating wrong BPMN 2.0 xml
        // (in case he or the exporting tool did not get that right)

        var pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/;
        var match = pattern.exec(err.message);

        if (match) {
            err.message =
                'unparsable content <' + match[1] + '> detected; ' +
                'this may indicate an invalid BPMN 2.0 diagram file' + match[2];
        }

        return err;
    }

    var DEFAULT_OPTIONS = {
        width: '100%',
        height: '100%',
        position: 'relative'
    };


    /**
     * Ensure the passed argument is a proper unit (defaulting to px)
     */
    function ensureUnit(val) {
        return val + (isNumber(val) ? 'px' : '');
    }


    /**
     * Find BPMNDiagram in definitions by ID
     *
     * @param {ModdleElement<Definitions>} definitions
     * @param {String} diagramId
     *
     * @return {ModdleElement<BPMNDiagram>|null}
     */
    function findBPMNDiagram(definitions, diagramId) {
        if (!diagramId) {
            return null;
        }

        return find(definitions.diagrams, function(element) {
            return element.id === diagramId;
        }) || null;
    }

    /**
     * A viewer for BPMN 2.0 diagrams.
     *
     * Have a look at {@link NavigatedViewer} or {@link Modeler} for bundles that include
     * additional features.
     *
     *
     * ## Extending the Viewer
     *
     * In order to extend the viewer pass extension modules to bootstrap via the
     * `additionalModules` option. An extension module is an object that exposes
     * named services.
     *
     * The following example depicts the integration of a simple
     * logging component that integrates with interaction events:
     *
     *
     * ```javascript
     *
     * // logging component
     * function InteractionLogger(eventBus) {
     *   eventBus.on('element.hover', function(event) {
     *     console.log()
     *   })
     * }
     *
     * InteractionLogger.$inject = [ 'eventBus' ]; // minification save
     *
     * // extension module
     * var extensionModule = {
     *   __init__: [ 'interactionLogger' ],
     *   interactionLogger: [ 'type', InteractionLogger ]
     * };
     *
     * // extend the viewer
     * var bpmnViewer = new Viewer({ additionalModules: [ extensionModule ] });
     * bpmnViewer.importXML(...);
     * ```
     *
     * @param {Object} [options] configuration options to pass to the viewer
     * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
     * @param {String|Number} [options.width] the width of the viewer
     * @param {String|Number} [options.height] the height of the viewer
     * @param {Object} [options.moddleExtensions] extension packages to provide
     * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
     * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
     */
    function Viewer(options) {

        options = assign({}, DEFAULT_OPTIONS, options);

        this._moddle = this._createModdle(options);

        this._container = this._createContainer(options);

        /* <project-logo> */

        addProjectLogo(this._container);

        /* </project-logo> */

        this._init(this._container, this._moddle, options);
    }

    inherits_browser(Viewer, Diagram);


    /**
     * Parse and render a BPMN 2.0 diagram.
     *
     * Once finished the viewer reports back the result to the
     * provided callback function with (err, warnings).
     *
     * ## Life-Cycle Events
     *
     * During import the viewer will fire life-cycle events:
     *
     *   * import.parse.start (about to read model from xml)
     *   * import.parse.complete (model read; may have worked or not)
     *   * import.render.start (graphical import start)
     *   * import.render.complete (graphical import finished)
     *   * import.done (everything done)
     *
     * You can use these events to hook into the life-cycle.
     *
     * @param {String} xml the BPMN 2.0 xml
     * @param {ModdleElement<BPMNDiagram>|String} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
     * @param {Function} [done] invoked with (err, warnings=[])
     */
    Viewer.prototype.importXML = function(xml, bpmnDiagram, done) {

        if (isFunction(bpmnDiagram)) {
            done = bpmnDiagram;
            bpmnDiagram = null;
        }

        // done is optional
        done = done || function() {};

        var self = this;

        // hook in pre-parse listeners +
        // allow xml manipulation
        xml = this._emit('import.parse.start', { xml: xml }) || xml;

        this._moddle.fromXML(xml, 'bpmn:Definitions', function(err, definitions, context) {

            // hook in post parse listeners +
            // allow definitions manipulation
            definitions = self._emit('import.parse.complete', {
                error: err,
                definitions: definitions,
                context: context
            }) || definitions;

            var parseWarnings = context.warnings;

            if (err) {
                err = checkValidationError(err);

                self._emit('import.done', { error: err, warnings: parseWarnings });

                return done(err, parseWarnings);
            }

            self.importDefinitions(definitions, bpmnDiagram, function(err, importWarnings) {
                var allWarnings = [].concat(parseWarnings, importWarnings || []);

                self._emit('import.done', { error: err, warnings: allWarnings });

                done(err, allWarnings);
            });
        });
    };

    /**
     * Import parsed definitions and render a BPMN 2.0 diagram.
     *
     * Once finished the viewer reports back the result to the
     * provided callback function with (err, warnings).
     *
     * ## Life-Cycle Events
     *
     * During import the viewer will fire life-cycle events:
     *
     *   * import.render.start (graphical import start)
     *   * import.render.complete (graphical import finished)
     *
     * You can use these events to hook into the life-cycle.
     *
     * @param {ModdleElement<Definitions>} definitions parsed BPMN 2.0 definitions
     * @param {ModdleElement<BPMNDiagram>|String} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
     * @param {Function} [done] invoked with (err, warnings=[])
     */
    Viewer.prototype.importDefinitions = function(definitions, bpmnDiagram, done) {

        if (isFunction(bpmnDiagram)) {
            done = bpmnDiagram;
            bpmnDiagram = null;
        }

        // done is optional
        done = done || function() {};

        this._setDefinitions(definitions);

        return this.open(bpmnDiagram, done);
    };

    /**
     * Open diagram of previously imported XML.
     *
     * Once finished the viewer reports back the result to the
     * provided callback function with (err, warnings).
     *
     * ## Life-Cycle Events
     *
     * During switch the viewer will fire life-cycle events:
     *
     *   * import.render.start (graphical import start)
     *   * import.render.complete (graphical import finished)
     *
     * You can use these events to hook into the life-cycle.
     *
     * @param {String|ModdleElement<BPMNDiagram>} [bpmnDiagramOrId] id or the diagram to open
     * @param {Function} [done] invoked with (err, warnings=[])
     */
    Viewer.prototype.open = function(bpmnDiagramOrId, done) {

        if (isFunction(bpmnDiagramOrId)) {
            done = bpmnDiagramOrId;
            bpmnDiagramOrId = null;
        }

        var definitions = this._definitions;
        var bpmnDiagram = bpmnDiagramOrId;

        // done is optional
        done = done || function() {};

        if (!definitions) {
            return done(new Error('no XML imported'));
        }

        if (typeof bpmnDiagramOrId === 'string') {
            bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId);

            if (!bpmnDiagram) {
                return done(new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found'));
            }
        }

        // clear existing rendered diagram
        // catch synchronous exceptions during #clear()
        try {
            this.clear();
        } catch (error) {
            return done(error);
        }

        // perform graphical import
        return importBpmnDiagram(this, definitions, bpmnDiagram, done);
    };

    /**
     * Export the currently displayed BPMN 2.0 diagram as
     * a BPMN 2.0 XML document.
     *
     * ## Life-Cycle Events
     *
     * During XML saving the viewer will fire life-cycle events:
     *
     *   * saveXML.start (before serialization)
     *   * saveXML.serialized (after xml generation)
     *   * saveXML.done (everything done)
     *
     * You can use these events to hook into the life-cycle.
     *
     * @param {Object} [options] export options
     * @param {Boolean} [options.format=false] output formated XML
     * @param {Boolean} [options.preamble=true] output preamble
     *
     * @param {Function} done invoked with (err, xml)
     */
    Viewer.prototype.saveXML = function(options, done) {

        if (!done) {
            done = options;
            options = {};
        }

        var self = this;

        var definitions = this._definitions;

        if (!definitions) {
            return done(new Error('no definitions loaded'));
        }

        // allow to fiddle around with definitions
        definitions = this._emit('saveXML.start', {
            definitions: definitions
        }) || definitions;

        this._moddle.toXML(definitions, options, function(err, xml) {

            try {
                xml = self._emit('saveXML.serialized', {
                    error: err,
                    xml: xml
                }) || xml;

                self._emit('saveXML.done', {
                    error: err,
                    xml: xml
                });
            } catch (e) {
                console.error('error in saveXML life-cycle listener', e);
            }

            done(err, xml);
        });
    };

    /**
     * Export the currently displayed BPMN 2.0 diagram as
     * an SVG image.
     *
     * ## Life-Cycle Events
     *
     * During SVG saving the viewer will fire life-cycle events:
     *
     *   * saveSVG.start (before serialization)
     *   * saveSVG.done (everything done)
     *
     * You can use these events to hook into the life-cycle.
     *
     * @param {Object} [options]
     * @param {Function} done invoked with (err, svgStr)
     */
    Viewer.prototype.saveSVG = function(options, done) {

        if (!done) {
            done = options;
            options = {};
        }

        this._emit('saveSVG.start');

        var svg, err;

        try {
            var canvas = this.get('canvas');

            var contentNode = canvas.getDefaultLayer(),
                defsNode = query('defs', canvas._svg);

            var contents = innerSVG(contentNode),
                defs = defsNode ? '<defs>' + innerSVG(defsNode) + '</defs>' : '';

            var bbox = contentNode.getBBox();

            svg =
                '<?xml version="1.0" encoding="utf-8"?>\n' +
                '<!-- created with bpmn-js / http://bpmn.io -->\n' +
                '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
                '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' +
                'width="' + bbox.width + '" height="' + bbox.height + '" ' +
                'viewBox="' + bbox.x + ' ' + bbox.y + ' ' + bbox.width + ' ' + bbox.height + '" version="1.1">' +
                defs + contents +
                '</svg>';
        } catch (e) {
            err = e;
        }

        this._emit('saveSVG.done', {
            error: err,
            svg: svg
        });

        done(err, svg);
    };

    /**
     * Get a named diagram service.
     *
     * @example
     *
     * var elementRegistry = viewer.get('elementRegistry');
     * var startEventShape = elementRegistry.get('StartEvent_1');
     *
     * @param {String} name
     *
     * @return {Object} diagram service instance
     *
     * @method Viewer#get
     */

    /**
     * Invoke a function in the context of this viewer.
     *
     * @example
     *
     * viewer.invoke(function(elementRegistry) {
     *   var startEventShape = elementRegistry.get('StartEvent_1');
     * });
     *
     * @param {Function} fn to be invoked
     *
     * @return {Object} the functions return value
     *
     * @method Viewer#invoke
     */


    Viewer.prototype._setDefinitions = function(definitions) {
        this._definitions = definitions;
    };

    Viewer.prototype.getModules = function() {
        return this._modules;
    };

    /**
     * Remove all drawn elements from the viewer.
     *
     * After calling this method the viewer can still
     * be reused for opening another diagram.
     *
     * @method Viewer#clear
     */
    Viewer.prototype.clear = function() {

        // remove businessObject#di binding
        //
        // this is necessary, as we establish the bindings
        // in the BpmnTreeWalker (and assume none are given
        // on reimport)
        this.get('elementRegistry').forEach(function(element) {
            var bo = element.businessObject;

            if (bo && bo.di) {
                delete bo.di;
            }
        });

        // remove drawn elements
        Diagram.prototype.clear.call(this);
    };

    /**
     * Destroy the viewer instance and remove all its
     * remainders from the document tree.
     */
    Viewer.prototype.destroy = function() {

        // diagram destroy
        Diagram.prototype.destroy.call(this);

        // dom detach
        remove(this._container);
    };

    /**
     * Register an event listener
     *
     * Remove a previously added listener via {@link #off(event, callback)}.
     *
     * @param {String} event
     * @param {Number} [priority]
     * @param {Function} callback
     * @param {Object} [that]
     */
    Viewer.prototype.on = function(event, priority, callback, target) {
        return this.get('eventBus').on(event, priority, callback, target);
    };

    /**
     * De-register an event listener
     *
     * @param {String} event
     * @param {Function} callback
     */
    Viewer.prototype.off = function(event, callback) {
        this.get('eventBus').off(event, callback);
    };

    Viewer.prototype.attachTo = function(parentNode) {

        if (!parentNode) {
            throw new Error('parentNode required');
        }

        // ensure we detach from the
        // previous, old parent
        this.detach();

        // unwrap jQuery if provided
        if (parentNode.get && parentNode.constructor.prototype.jquery) {
            parentNode = parentNode.get(0);
        }

        if (typeof parentNode === 'string') {
            parentNode = query(parentNode);
        }

        parentNode.appendChild(this._container);

        this._emit('attach', {});

        this.get('canvas').resized();
    };

    Viewer.prototype.getDefinitions = function() {
        return this._definitions;
    };

    Viewer.prototype.detach = function() {

        var container = this._container,
            parentNode = container.parentNode;

        if (!parentNode) {
            return;
        }

        this._emit('detach', {});

        parentNode.removeChild(container);
    };

    Viewer.prototype._init = function(container, moddle, options) {

        var baseModules = options.modules || this.getModules(),
            additionalModules = options.additionalModules || [],
            staticModules = [
                {
                    bpmnjs: [ 'value', this ],
                    moddle: [ 'value', moddle ]
                }
            ];

        var diagramModules = [].concat(staticModules, baseModules, additionalModules);

        var diagramOptions = assign(omit(options, [ 'additionalModules' ]), {
            canvas: assign({}, options.canvas, { container: container }),
            modules: diagramModules
        });

        // invoke diagram constructor
        Diagram.call(this, diagramOptions);

        if (options && options.container) {
            this.attachTo(options.container);
        }
    };

    /**
     * Emit an event on the underlying {@link EventBus}
     *
     * @param  {String} type
     * @param  {Object} event
     *
     * @return {Object} event processing result (if any)
     */
    Viewer.prototype._emit = function(type, event) {
        return this.get('eventBus').fire(type, event);
    };

    Viewer.prototype._createContainer = function(options) {

        var container = domify('<div class="bjs-container"></div>');

        assign(container.style, {
            width: ensureUnit(options.width),
            height: ensureUnit(options.height),
            position: options.position
        });

        return container;
    };

    Viewer.prototype._createModdle = function(options) {
        var moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions);

        return new BpmnModdle$1(moddleOptions);
    };

    // modules the viewer is composed of
    Viewer.prototype._modules = [
        CoreModule$1,
        TranslateModule,
        SelectionModule,
        OverlaysModule
    ];

    // default moddle extensions the viewer is composed of
    Viewer.prototype._moddleExtensions = {};

    /**
     * Adds the project logo to the diagram container as
     * required by the bpmn.io license.
     *
     * @see http://bpmn.io/license
     *
     * @param {Element} container
     */
    function addProjectLogo(container) {
        // var img = BPMNIO_IMG;
        //
        // var linkMarkup =
        //     '<a href="http://bpmn.io" ' +
        //     'target="_blank" ' +
        //     'class="bjs-powered-by" ' +
        //     'title="Powered by bpmn.io" ' +
        //     'style="position: absolute; bottom: 15px; right: 15px; z-index: 100">' +
        //     img +
        //     '</a>';
        //
        // var linkElement = domify(linkMarkup);
        //
        // container.appendChild(linkElement);
        //
        // componentEvent.bind(linkElement, 'click', function(event) {
        //     open();
        //
        //     event.preventDefault();
        // });
    }

    /* </project-logo> */

    /**
     * Returns true if event was triggered with any modifier
     * @param {KeyboardEvent} event
     */
    function hasModifier(event) {
        return (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey);
    }

    /**
     * @param {KeyboardEvent} event
     */
    function isCmd(event) {

        // ensure we don't react to AltGr
        // (mapped to CTRL + ALT)
        if (event.altKey) {
            return false;
        }

        return event.ctrlKey || event.metaKey;
    }

    /**
     * Checks if key pressed is one of provided keys.
     *
     * @param {String|String[]} keys
     * @param {KeyboardEvent} event
     */
    function isKey(keys, event) {
        keys = isArray(keys) ? keys : [ keys ];

        return keys.indexOf(event.key) > -1;
    }

    /**
     * @param {KeyboardEvent} event
     */
    function isShift(event) {
        return event.shiftKey;
    }

    var KEYDOWN_EVENT = 'keyboard.keydown',
        KEYUP_EVENT = 'keyboard.keyup';

    var DEFAULT_PRIORITY$1 = 1000;


    /**
     * A keyboard abstraction that may be activated and
     * deactivated by users at will, consuming key events
     * and triggering diagram actions.
     *
     * For keys pressed down, keyboard fires `keyboard.keydown` event.
     * The event context contains one field which is `KeyboardEvent` event.
     *
     * The implementation fires the following key events that allow
     * other components to hook into key handling:
     *
     *  - keyboard.bind
     *  - keyboard.unbind
     *  - keyboard.init
     *  - keyboard.destroy
     *
     * All events contain one field which is node.
     *
     * A default binding for the keyboard may be specified via the
     * `keyboard.bindTo` configuration option.
     *
     * @param {Config} config
     * @param {EventBus} eventBus
     */
    function Keyboard(config, eventBus) {
        var self = this;

        this._config = config || {};
        this._eventBus = eventBus;

        this._keydownHandler = this._keydownHandler.bind(this);
        this._keyupHandler = this._keyupHandler.bind(this);

        // properly clean dom registrations
        eventBus.on('diagram.destroy', function() {
            self._fire('destroy');

            self.unbind();
        });

        eventBus.on('diagram.init', function() {
            self._fire('init');
        });

        eventBus.on('attach', function() {
            if (config && config.bindTo) {
                self.bind(config.bindTo);
            }
        });

        eventBus.on('detach', function() {
            self.unbind();
        });
    }

    Keyboard.$inject = [
        'config.keyboard',
        'eventBus'
    ];

    Keyboard.prototype._keydownHandler = function(event) {
        this._keyHandler(event, KEYDOWN_EVENT);
    };

    Keyboard.prototype._keyupHandler = function(event) {
        this._keyHandler(event, KEYUP_EVENT);
    };

    Keyboard.prototype._keyHandler = function(event, type) {
        var target = event.target,
            eventBusResult;

        if (isInput(target)) {
            return;
        }

        var context = {
            keyEvent: event
        };

        eventBusResult = this._eventBus.fire(type || KEYDOWN_EVENT, context);

        if (eventBusResult) {
            event.preventDefault();
        }
    };

    Keyboard.prototype.bind = function(node) {

        // make sure that the keyboard is only bound once to the DOM
        this.unbind();

        this._node = node;

        // bind key events
        componentEvent.bind(node, 'keydown', this._keydownHandler, true);
        componentEvent.bind(node, 'keyup', this._keyupHandler, true);

        this._fire('bind');
    };

    Keyboard.prototype.getBinding = function() {
        return this._node;
    };

    Keyboard.prototype.unbind = function() {
        var node = this._node;

        if (node) {
            this._fire('unbind');

            // unbind key events
            componentEvent.unbind(node, 'keydown', this._keydownHandler, true);
            componentEvent.unbind(node, 'keyup', this._keyupHandler, true);
        }

        this._node = null;
    };

    Keyboard.prototype._fire = function(event) {
        this._eventBus.fire('keyboard.' + event, { node: this._node });
    };

    /**
     * Add a listener function that is notified with `KeyboardEvent` whenever
     * the keyboard is bound and the user presses a key. If no priority is
     * provided, the default value of 1000 is used.
     *
     * @param {Number} [priority]
     * @param {Function} listener
     * @param {string} type
     */
    Keyboard.prototype.addListener = function(priority, listener, type) {
        if (isFunction(priority)) {
            type = listener;
            listener = priority;
            priority = DEFAULT_PRIORITY$1;
        }

        this._eventBus.on(type || KEYDOWN_EVENT, priority, listener);
    };

    Keyboard.prototype.removeListener = function(listener, type) {
        this._eventBus.off(type || KEYDOWN_EVENT, listener);
    };

    Keyboard.prototype.hasModifier = hasModifier;
    Keyboard.prototype.isCmd = isCmd;
    Keyboard.prototype.isShift = isShift;
    Keyboard.prototype.isKey = isKey;



    // helpers ///////

    function isInput(target) {
        return target && (matchesSelector$1(target, 'input, textarea') || target.contentEditable === 'true');
    }

    var LOW_PRIORITY$3 = 500;


    /**
     * Adds default keyboard bindings.
     *
     * This does not pull in any features will bind only actions that
     * have previously been registered against the editorActions component.
     *
     * @param {EventBus} eventBus
     * @param {Keyboard} keyboard
     */
    function KeyboardBindings(eventBus, keyboard) {

        var self = this;

        eventBus.on('editorActions.init', LOW_PRIORITY$3, function(event) {

            var editorActions = event.editorActions;

            self.registerBindings(keyboard, editorActions);
        });
    }

    KeyboardBindings.$inject = [
        'eventBus',
        'keyboard'
    ];


    /**
     * Register available keyboard bindings.
     *
     * @param {Keyboard} keyboard
     * @param {EditorActions} editorActions
     */
    KeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) {

        /**
         * Add keyboard binding if respective editor action
         * is registered.
         *
         * @param {String} action name
         * @param {Function} fn that implements the key binding
         */
        function addListener(action, fn) {

            if (editorActions.isRegistered(action)) {
                keyboard.addListener(fn);
            }
        }


        // undo
        // (CTRL|CMD) + Z
        addListener('undo', function(context) {

            var event = context.keyEvent;

            if (isCmd(event) && !isShift(event) && isKey(['z', 'Z'], event)) {
                editorActions.trigger('undo');

                return true;
            }
        });

        // redo
        // CTRL + Y
        // CMD + SHIFT + Z
        addListener('redo', function(context) {

            var event = context.keyEvent;

            if (isCmd(event) && (isKey(['y', 'Y'], event) || (isKey(['z', 'Z'], event) && isShift(event)))) {
                editorActions.trigger('redo');

                return true;
            }
        });

        // copy
        // CTRL/CMD + C
        addListener('copy', function(context) {

            var event = context.keyEvent;

            if (isCmd(event) && isKey(['c', 'C'], event)) {
                editorActions.trigger('copy');

                return true;
            }
        });

        // paste
        // CTRL/CMD + V
        addListener('paste', function(context) {

            var event = context.keyEvent;

            if (isCmd(event) && isKey(['v', 'V'], event)) {
                editorActions.trigger('paste');

                return true;
            }
        });

        // zoom in one step
        // CTRL/CMD + +
        addListener('stepZoom', function(context) {

            var event = context.keyEvent;

            if (isKey([ '+', 'Add' ], event) && isCmd(event)) {
                editorActions.trigger('stepZoom', { value: 1 });

                return true;
            }
        });

        // zoom out one step
        // CTRL + -
        addListener('stepZoom', function(context) {

            var event = context.keyEvent;

            if (isKey([ '-', 'Subtract' ], event) && isCmd(event)) {
                editorActions.trigger('stepZoom', { value: -1 });

                return true;
            }
        });

        // zoom to the default level
        // CTRL + 0
        addListener('zoom', function(context) {

            var event = context.keyEvent;

            if (isKey('0', event) && isCmd(event)) {
                editorActions.trigger('zoom', { value: 1 });

                return true;
            }
        });

        // delete selected element
        // DEL
        addListener('removeSelection', function(context) {

            var event = context.keyEvent;

            if (isKey([ 'Delete', 'Del' ], event)) {
                editorActions.trigger('removeSelection');

                return true;
            }
        });
    };

    var KeyboardModule = {
        __init__: [ 'keyboard', 'keyboardBindings' ],
        keyboard: [ 'type', Keyboard ],
        keyboardBindings: [ 'type', KeyboardBindings ]
    };

    var DEFAULT_CONFIG = {
        moveSpeed: 50,
        moveSpeedAccelerated: 200
    };


    /**
     * A feature that allows users to move the canvas using the keyboard.
     *
     * @param {Object} config
     * @param {Number} [config.moveSpeed=50]
     * @param {Number} [config.moveSpeedAccelerated=200]
     * @param {Keyboard} keyboard
     * @param {Canvas} canvas
     */
    function KeyboardMove(
        config,
        keyboard,
        canvas
    ) {

        var self = this;

        this._config = assign({}, DEFAULT_CONFIG, config || {});

        keyboard.addListener(arrowsListener);


        function arrowsListener(context) {

            var event = context.keyEvent,
                config = self._config;

            if (!keyboard.isCmd(event)) {
                return;
            }

            if (keyboard.isKey([
                'ArrowLeft', 'Left',
                'ArrowUp', 'Up',
                'ArrowDown', 'Down',
                'ArrowRight', 'Right'
            ], event)) {

                var speed = (
                    keyboard.isShift(event) ?
                        config.moveSpeedAccelerated :
                        config.moveSpeed
                );

                var direction;

                switch (event.key) {
                    case 'ArrowLeft':
                    case 'Left':
                        direction = 'left';
                        break;
                    case 'ArrowUp':
                    case 'Up':
                        direction = 'up';
                        break;
                    case 'ArrowRight':
                    case 'Right':
                        direction = 'right';
                        break;
                    case 'ArrowDown':
                    case 'Down':
                        direction = 'down';
                        break;
                }

                self.moveCanvas({
                    speed: speed,
                    direction: direction
                });

                return true;
            }
        }

        this.moveCanvas = function(opts) {

            var dx = 0,
                dy = 0,
                speed = opts.speed;

            var actualSpeed = speed / Math.min(Math.sqrt(canvas.viewbox().scale), 1);

            switch (opts.direction) {
                case 'left': // Left
                    dx = actualSpeed;
                    break;
                case 'up': // Up
                    dy = actualSpeed;
                    break;
                case 'right': // Right
                    dx = -actualSpeed;
                    break;
                case 'down': // Down
                    dy = -actualSpeed;
                    break;
            }

            canvas.scroll({
                dx: dx,
                dy: dy
            });
        };

    }


    KeyboardMove.$inject = [
        'config.keyboardMove',
        'keyboard',
        'canvas'
    ];

    var KeyboardMoveModule = {
        __depends__: [
            KeyboardModule
        ],
        __init__: [ 'keyboardMove' ],
        keyboardMove: [ 'type', KeyboardMove ]
    };

    var CURSOR_CLS_PATTERN = /^djs-cursor-.*$/;


    function set$1(mode) {
        var classes$1 = classes(document.body);

        classes$1.removeMatching(CURSOR_CLS_PATTERN);

        if (mode) {
            classes$1.add('djs-cursor-' + mode);
        }
    }

    function unset() {
        set$1(null);
    }

    var TRAP_PRIORITY = 5000;

    /**
     * Installs a click trap that prevents a ghost click following a dragging operation.
     *
     * @return {Function} a function to immediately remove the installed trap.
     */
    function install(eventBus, eventName) {

        eventName = eventName || 'element.click';

        function trap() {
            return false;
        }

        eventBus.once(eventName, TRAP_PRIORITY, trap);

        return function() {
            eventBus.off(eventName, trap);
        };
    }

    function delta(a, b) {
        return {
            x: a.x - b.x,
            y: a.y - b.y
        };
    }

    var THRESHOLD = 15;


    /**
     * Move the canvas via mouse.
     *
     * @param {EventBus} eventBus
     * @param {Canvas} canvas
     */
    function MoveCanvas(eventBus, canvas) {

        var context;


        // listen for move on element mouse down;
        // allow others to hook into the event before us though
        // (dragging / element moving will do this)
        eventBus.on('element.mousedown', 500, function(e) {
            return handleStart(e.originalEvent);
        });


        function handleMove(event) {

            var start = context.start,
                position = toPoint(event),
                delta$1 = delta(position, start);

            if (!context.dragging && length(delta$1) > THRESHOLD) {
                context.dragging = true;

                install(eventBus);

                set$1('grab');
            }

            if (context.dragging) {

                var lastPosition = context.last || context.start;

                delta$1 = delta(position, lastPosition);

                canvas.scroll({
                    dx: delta$1.x,
                    dy: delta$1.y
                });

                context.last = position;
            }

            // prevent select
            event.preventDefault();
        }


        function handleEnd(event) {
            componentEvent.unbind(document, 'mousemove', handleMove);
            componentEvent.unbind(document, 'mouseup', handleEnd);

            context = null;

            unset();
        }

        function handleStart(event) {
            // event is already handled by '.djs-draggable'
            if (closest(event.target, '.djs-draggable')) {
                return;
            }


            // reject non-left left mouse button or modifier key
            if (event.button || event.ctrlKey || event.shiftKey || event.altKey) {
                return;
            }

            context = {
                start: toPoint(event)
            };

            componentEvent.bind(document, 'mousemove', handleMove);
            componentEvent.bind(document, 'mouseup', handleEnd);

            // we've handled the event
            return true;
        }
    }


    MoveCanvas.$inject = [
        'eventBus',
        'canvas'
    ];



    // helpers ///////

    function length(point) {
        return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2));
    }

    var MoveCanvasModule = {
        __init__: [ 'moveCanvas' ],
        moveCanvas: [ 'type', MoveCanvas ]
    };

    /**
     * Get the logarithm of x with base 10
     * @param  {Integer} value
     */
    function log10(x) {
        return Math.log(x) / Math.log(10);
    }

    /**
     * Get step size for given range and number of steps.
     *
     * @param {Object} range
     * @param {number} range.min
     * @param {number} range.max
     */
    function getStepSize(range, steps) {

        var minLinearRange = log10(range.min),
            maxLinearRange = log10(range.max);

        var absoluteLinearRange = Math.abs(minLinearRange) + Math.abs(maxLinearRange);

        return absoluteLinearRange / steps;
    }

    function cap(range, scale) {
        return Math.max(range.min, Math.min(range.max, scale));
    }

    var sign = Math.sign || function(n) {
        return n >= 0 ? 1 : -1;
    };

    var RANGE = { min: 0.2, max: 4 },
        NUM_STEPS = 10;

    var DELTA_THRESHOLD = 0.1;

    var DEFAULT_SCALE = 0.75;

    /**
     * An implementation of zooming and scrolling within the
     * {@link Canvas} via the mouse wheel.
     *
     * Mouse wheel zooming / scrolling may be disabled using
     * the {@link toggle(enabled)} method.
     *
     * @param {Object} [config]
     * @param {Boolean} [config.enabled=true] default enabled state
     * @param {Number} [config.scale=.75] scroll sensivity
     * @param {EventBus} eventBus
     * @param {Canvas} canvas
     */
    function ZoomScroll(config, eventBus, canvas) {

        config = config || {};

        this._enabled = false;

        this._canvas = canvas;
        this._container = canvas._container;

        this._handleWheel = bind(this._handleWheel, this);

        this._totalDelta = 0;
        this._scale = config.scale || DEFAULT_SCALE;

        var self = this;

        eventBus.on('canvas.init', function(e) {
            self._init(config.enabled !== false);
        });
    }

    ZoomScroll.$inject = [
        'config.zoomScroll',
        'eventBus',
        'canvas'
    ];

    ZoomScroll.prototype.scroll = function scroll(delta) {
        this._canvas.scroll(delta);
    };


    ZoomScroll.prototype.reset = function reset() {
        this._canvas.zoom('fit-viewport');
    };

    /**
     * Zoom depending on delta.
     *
     * @param {number} delta
     * @param {Object} position
     */
    ZoomScroll.prototype.zoom = function zoom(delta, position) {

        // zoom with half the step size of stepZoom
        var stepSize = getStepSize(RANGE, NUM_STEPS * 2);

        // add until threshold reached
        this._totalDelta += delta;

        if (Math.abs(this._totalDelta) > DELTA_THRESHOLD) {
            this._zoom(delta, position, stepSize);

            // reset
            this._totalDelta = 0;
        }
    };


    ZoomScroll.prototype._handleWheel = function handleWheel(event) {
        // event is already handled by '.djs-scrollable'
        if (closest(event.target, '.djs-scrollable', true)) {
            return;
        }

        var element = this._container;

        event.preventDefault();

        // pinch to zoom is mapped to wheel + ctrlKey = true
        // in modern browsers (!)

        var isZoom = event.ctrlKey;

        var isHorizontalScroll = event.shiftKey;

        var factor = -1 * this._scale,
            delta;

        if (isZoom) {
            factor *= event.deltaMode === 0 ? 0.020 : 0.32;
        } else {
            factor *= event.deltaMode === 0 ? 1.0 : 16.0;
        }

        if (isZoom) {
            var elementRect = element.getBoundingClientRect();

            var offset = {
                x: event.clientX - elementRect.left,
                y: event.clientY - elementRect.top
            };

            delta = (
                Math.sqrt(
                    Math.pow(event.deltaY, 2) +
                    Math.pow(event.deltaX, 2)
                ) * sign(event.deltaY) * factor
            );

            // zoom in relative to diagram {x,y} coordinates
            this.zoom(delta, offset);
        } else {

            if (isHorizontalScroll) {
                delta = {
                    dx: factor * event.deltaY,
                    dy: 0
                };
            } else {
                delta = {
                    dx: factor * event.deltaX,
                    dy: factor * event.deltaY
                };
            }

            this.scroll(delta);
        }
    };

    /**
     * Zoom with fixed step size.
     *
     * @param {number} delta - Zoom delta (1 for zooming in, -1 for out).
     * @param {Object} position
     */
    ZoomScroll.prototype.stepZoom = function stepZoom(delta, position) {

        var stepSize = getStepSize(RANGE, NUM_STEPS);

        this._zoom(delta, position, stepSize);
    };


    /**
     * Zoom in/out given a step size.
     *
     * @param {number} delta
     * @param {Object} position
     * @param {number} stepSize
     */
    ZoomScroll.prototype._zoom = function(delta, position, stepSize) {
        var canvas = this._canvas;

        var direction = delta > 0 ? 1 : -1;

        var currentLinearZoomLevel = log10(canvas.zoom());

        // snap to a proximate zoom step
        var newLinearZoomLevel = Math.round(currentLinearZoomLevel / stepSize) * stepSize;

        // increase or decrease one zoom step in the given direction
        newLinearZoomLevel += stepSize * direction;

        // calculate the absolute logarithmic zoom level based on the linear zoom level
        // (e.g. 2 for an absolute x2 zoom)
        var newLogZoomLevel = Math.pow(10, newLinearZoomLevel);

        canvas.zoom(cap(RANGE, newLogZoomLevel), position);
    };


    /**
     * Toggle the zoom scroll ability via mouse wheel.
     *
     * @param  {Boolean} [newEnabled] new enabled state
     */
    ZoomScroll.prototype.toggle = function toggle(newEnabled) {

        var element = this._container;
        var handleWheel = this._handleWheel;

        var oldEnabled = this._enabled;

        if (typeof newEnabled === 'undefined') {
            newEnabled = !oldEnabled;
        }

        // only react on actual changes
        if (oldEnabled !== newEnabled) {

            // add or remove wheel listener based on
            // changed enabled state
            componentEvent[newEnabled ? 'bind' : 'unbind'](element, 'wheel', handleWheel, false);
        }

        this._enabled = newEnabled;

        return newEnabled;
    };


    ZoomScroll.prototype._init = function(newEnabled) {
        this.toggle(newEnabled);
    };

    var ZoomScrollModule = {
        __init__: [ 'zoomScroll' ],
        zoomScroll: [ 'type', ZoomScroll ]
    };

    /**
     * A viewer that includes mouse navigation facilities
     *
     * @param {Object} options
     */
    function NavigatedViewer(options) {
        Viewer.call(this, options);
    }

    inherits_browser(NavigatedViewer, Viewer);

    NavigatedViewer.prototype._navigationModules = [
        KeyboardMoveModule,
        MoveCanvasModule,
        ZoomScrollModule
    ];

    NavigatedViewer.prototype._modules = [].concat(
        NavigatedViewer.prototype._modules,
        NavigatedViewer.prototype._navigationModules);

    return NavigatedViewer;

}));